题解
按照题意将节点之间连线后 每个联通块无环但是整体不一定联通 使用并查集将每个集合都跟0号节点连一根线 建立以0为根的树
使用树形dp d[i][0/1]表示以i为根的子树i这个节点1选/0不选的最大收益 DFS从0遍历整棵树回溯后计算d
如果不选当前节点x则直接加上子节点y选或者不选 即d[x][0] += max(d[y][0], d[y][1])
如果选当前节点x从子节点y转移时候 如果选y择需要减去一部分收益 即d[x][1] += max(d[y][0], d[y][1] - g[min(x, y)])
AC代码
#include <stdio.h>
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int MAXN = 1e2 + 10;
int vex[MAXN];
int f[MAXN], g[MAXN];
int d[MAXN][2]; //以i为根的子树1选/0不选的最大收益
vector<int> e[MAXN];
inline int findr(int x)
{
return vex[x] == 0 ? x : vex[x] = findr(vex[x]);
}
inline void join(int x, int y)
{
vex[findr(y)] = findr(x);
}
void DFS(int x, int fz)
{
d[x][1] = f[x]; //选x
for (int y : e[x]) if (y != fz)
{
DFS(y, x);
d[x][0] += max(d[y][0], d[y][1]); //加上子节点选或不选
d[x][1] += max(d[y][0], d[y][1] - g[min(x, y)]); //都选损失g[min(x, y)]
}
}
int main()
{
#ifdef LOCAL
freopen("C:/input.txt", "r", stdin);
#endif
int n;
cin >> n;
for (int i = 1; i <= n; i++)
scanf("%d", &f[i]);
for (int i = 1; i <= n; i++)
scanf("%d", &g[i]);
for (int i = 2; i <= n; i++)
if ((i & 1) == 0) //奇偶建边
e[i].push_back(i / 2), e[i / 2].push_back(i), join(i, i / 2);
else if (i * 3 + 1 <= n)
e[i].push_back(i * 3 + 1), e[i * 3 + 1].push_back(i), join(i, i * 3 + 1);
for (int i = 1; i <= n; i++)
if (!vex[i]) //最后将每个集合和0号节点建立一个连线
e[0].push_back(i), e[i].push_back(0);
DFS(0, -1);
cout << d[0][0] << endl; //输出不选根
return 0;
}

本文深入解析了一道使用树形DP结合并查集算法解决的问题,通过构建以0为根的树,利用DFS遍历整棵树进行回溯计算,详细介绍了如何在选择或不选择当前节点的情况下,计算最大收益。文章提供了完整的AC代码实现。
490

被折叠的 条评论
为什么被折叠?



