P2607 骑士

题意

nn 个点,第 ii 个点连着 aia_i 点。其中每个点都有一个权值。对于每条边而言,其两端的点不可以同时加入总和。我们要求总和最大值。

思路

可以借鉴之前的blog

首先,这个图可以看出是一个个基环数组成。我们要求的就是所有基环数的总和最大值的和。

对于一个基环树,我们有入下图所示的样子。

alt

从图中我们不难发现,我们有如下一个环,[1,12,7,14,4,11] ,如果我们断开其中一个路径,那么他的样子就成为树了,我们断开 [1,11] 这条路径。分别以 11 为根和以 1111 为根做树形 dp。

先忽略环的影响,那么我们对于如下的树我们该怎么计算才能得到正确答案呢?

alt

我们设 f[i,0/1]

f[i,0] 表示第 ii 个点不包含的最大值,f[i,1] 表示第 ii 个点包含的最大值。那么我们就得到如下的状态转移方程。

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]可以将 1111 这个点的所有状态包含进去了,由于我们的 111111 不可以同时加入,所以我们取 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;
}