P2607 骑士
题意
有 个点,第 个点连着 点。其中每个点都有一个权值。对于每条边而言,其两端的点不可以同时加入总和。我们要求总和最大值。
思路
可以借鉴之前的blog
首先,这个图可以看出是一个个基环数组成。我们要求的就是所有基环数的总和最大值的和。
对于一个基环树,我们有入下图所示的样子。
从图中我们不难发现,我们有如下一个环,[1,12,7,14,4,11]
,如果我们断开其中一个路径,那么他的样子就成为树了,我们断开 [1,11]
这条路径。分别以 为根和以 为根做树形 dp。
先忽略环的影响,那么我们对于如下的树我们该怎么计算才能得到正确答案呢?
我们设 f[i,0/1]
f[i,0]
表示第 个点不包含的最大值,f[i,1]
表示第 个点包含的最大值。那么我们就得到如下的状态转移方程。
for (auto x : E[u]) {
f[u][0] += max(f[x][1] , f[x][0]);
f[u][1] += f[x][0];
}
那么最后,这棵树的最大总和即为 max(f[1,0] , f[1,1])
。
我们发现,对于这棵树我们的 f[1,1]
可以将 这个点的所有状态包含进去了,由于我们的 和 不可以同时加入,所以我们取 f[1,0]
作为最后的决策之一。我们再次通过相同的步骤计算出 f[12,0]
那么我们的答案就是这两个中的最大值了。
code
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
const int N = 1e6 + 10;
long long w[N];
long long f[N][2];
bool vis[N];
int s, t;
vector<int> E[N];
void dfs(int u, int fa = -1) {
vis[u] = true;
int cnt = 0;
for (auto x : E[u]) {
if (x == fa && ++cnt < 2) continue;
if (x == t) continue;
if (vis[x]) {
s = x;
t = u;
continue;
}
dfs(x, u);
}
}
void calc(int u, int fa = -1) {
int cnt = 0;
f[u][0] = 0;
f[u][1] = w[u];
for (auto x : E[u]) {
if (x == fa) continue;
if (u == s && x == t && ++cnt < 2) continue;
if (x == s) continue;
calc(x, u);
f[u][0] += max(f[x][1], f[x][0]);
f[u][1] += f[x][0];
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
long long ans = 0;
for (int i = 1; i <= n; i++) {
cin >> w[i];
int x;
cin >> x;
E[i].push_back(x);
E[x].push_back(i);
}
for (int i = 1; i <= n; i++) {
if (vis[i]) continue;
dfs(i);
calc(s);
long long maxx = f[s][0];
swap(s, t);
calc(s);
maxx = max(maxx, f[s][0]);
ans += maxx;
}
cout << ans << '\n';
return 0;
}