P2607 [ZJOI2008]骑士
P2607 [ZJOI2008]骑士
题目链接
基环树DP。
我们可以把\(x\)的仇人\(y\)向\(x\)连一条边,这样会形成好多联通块,每个联通块上有个基环树。
对于基环树的题,大体思路都是断掉环上的一条边,把它当成树来做。
假设现在已经断掉了一条边,那么转移方程就是:\(f[x][0] += max(f[y][1], f[y][0]); \ f[x][1] += f[y][0];\)(树形DP)
对于每个联通块上面的环,我们断掉环上的任意一条边得到的结果都是一样的。
假设我们现在断掉一条边链接\(x, y\)两个点,我们分两种情况,强制选\(x\)不选\(y\),强制选\(y\)不选\(x\)。我们要统计答案的一定是\(f[x][0]\),因为如果是\(f[x][1]\)的话,我们不能知道\(y\)是否选上了。
#include <bits/stdc++.h>
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 1e6 + 5;
int n, cnt, root;
int fa[N], vis[N], val[N], head[N];
long long ans, f[N][2];
struct edge { int to, nxt; } e[N];
void add(int x, int y) {
e[++cnt].nxt = head[x]; head[x] = cnt; e[cnt].to = y;
}
void DP(int x) {
vis[x] = 1;
f[x][0] = 0; f[x][1] = val[x];
for(int i = head[x]; i ; i = e[i].nxt) {
int y = e[i].to;
if(y != root) {
DP(y);
f[x][0] += max(f[y][1], f[y][0]);
f[x][1] += f[y][0];
}
}
}
void work(int x) {
vis[root = x] = 1;
while(!vis[fa[root]]) root = fa[root], vis[root] = 1;
DP(root);
long long tmp = f[root][0];
DP(root = fa[root]);
ans += max(tmp, f[root][0]);
}
int main() {
n = read();
for(int i = 1, x; i <= n; ++ i) {
val[i] = read(); fa[i] = x = read();
add(x, i);
}
for(int i = 1;i <= n; i++) if(!vis[i]) work(i);
printf("%lld", ans);
return 0;
}