BZOJ1040: [ZJOI2008]骑士 (树形DP)
题意:1e6个骑士 每个骑士有一个战斗力有一个讨厌的人
选择一些人组成军团使得军团战斗力和最大 且每个人不会和自己讨厌的人都在军团里
题解:每个点和自己讨厌的点建一条双向边 因为你讨厌他 你不会和他同时出现
n个点n条边 那么每一个独立的子集都是一棵带环树
先dfs一遍找到形成环的地方 把他断掉
分别以两个点做一次不能选相邻两个点的树形DP 且不选起点
总结:树上的操作还是不熟 怎么找环断开边什么的
#include <stdio.h> #include <algorithm> #include <iostream> #include <string.h> using namespace std; typedef long long ll; int n; struct node { int to, nex; }E[2000005]; int head[2000005]; int vis[1000005]; int s[2000005]; ll q[1000005]; ll dp[1000005][3]; int l, r; bool f; void dfs(int x, int fa) { vis[x] = 1; int c = head[x]; for(int i = c; i && (!f); i = E[i].nex) { int v = E[i].to; if(v == fa) continue; if(vis[v]) { s[i] = -1; //表示将这两条边断开 if((i & 1) == 1) s[i + 1] = -1; else s[i - 1] = -1; l = x; r = v; f = true; break; } dfs(v, x); } } void dfs1(int x, int fa, int cut) { vis[x] = true; //可能找到环了 但这颗带环树还没搜完 if(x != cut) dp[x][1] = q[x]; else dp[x][1] = 0; dp[x][0] = 0; int c = head[x]; for(int i = c; i; i = E[i].nex) { int v = E[i].to; if(v == fa) continue; if(s[i] == -1) continue; dfs1(v, x, cut); dp[x][1] += dp[v][0]; dp[x][0] += max(dp[v][1], dp[v][0]); } } int main() { int cnt = 0; scanf("%d", &n); for(int i = 1; i <= n; i++) { int x; scanf("%lld%d", &q[i], &x); E[++cnt].to = x, E[cnt].nex = head[i], head[i] = cnt; E[++cnt].to = i, E[cnt].nex = head[x], head[x] = cnt; } ll ans = 0; for(int i = 1; i <= n; i++) { if(!vis[i]) { f = false; //任意独立x个点x条边 一定带环 所以不存在树了 dfs(i, -1); ll o = 0; dfs1(l, -1, r); o = max(o, dp[l][0]); o = max(o, dp[l][1]); dfs1(r, -1, l); o = max(o, dp[r][0]); o = max(o, dp[r][1]); ans += o; } } printf("%lld\n", ans); return 0; } /* 5 10 2 10 3 10 4 10 2 10 4 5 10 2 25 3 10 4 16 2 10 4 */