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;
}
原文地址:https://www.cnblogs.com/czhui666/p/13641990.html