1. 程式人生 > 實用技巧 >SP5971 LCMSUM - LCM Sum

SP5971 LCMSUM - LCM Sum

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;
}