BZOJ 1040: [ZJOI2008]骑士(基环树dp)
http://www.lydsy.com/JudgeOnline/problem.php?id=1040
题意:
思路:
这是基环树,因为每个人只会有一个厌恶的人,所以每个节点只会有一个父亲节点,但是根节点也是有父亲节点的,所以在树中肯定是存在一个环的,只要删除该环中的任意一条边,那么就能将该图变成一颗树。
如果是树的话,那就很简单了,d[u][0/1] dp求解即可。
现在假设删除的边是e,两端的节点分别是u,v,首先对u为根的树作一次dp,最后取d[u][0](v取不取都无所谓),不能取d[u][1](因为此时可能也取了v)。但是这样的话没有考虑选u的情况,所以再对v为根的树作一次dp,最后取d[v][0]。两者取大者即可。
#include<cstdio> #include<iostream> #include<cstring> #include<algorithm> #include<map> using namespace std; const int maxn = 1000000+5; typedef long long ll; int n,tot=0,edgeID,edgeLeft,edgeRight; int head[maxn],vis[maxn]; ll val[maxn], d[maxn][2]; struct node { int v,next; }e[2*maxn]; void addEdge(int u,int v) { e[tot].v = v; e[tot].next = head[u]; head[u] = tot++; } void dfs(int u, int fa) { vis[u] = 1; for(int i=head[u];i!=-1;i=e[i].next) { int v = e[i].v; if(v == fa) continue; if(!vis[v]) dfs(v,u); else //找到了环 { edgeID = i; //记录边和两端顶点 edgeLeft = u; edgeRight = v; } } } ll dp(int u, int fa) { d[u][0] = 0, d[u][1] = val[u]; for(int i=head[u];i!=-1;i=e[i].next) { int v = e[i].v; if(v==fa) continue; if(i==edgeID || i==(edgeID^1)) continue; //正向边和反向边 dp(v,u); d[u][0] += max(d[v][0],d[v][1]); d[u][1] += d[v][0]; } return d[u][0]; } int main() { //freopen("in.txt","r",stdin); memset(head,-1,sizeof(head)); scanf("%d",&n); for(int i=1;i<=n;i++) { int x; scanf("%lld%d",&val[i],&x); addEdge(i,x); addEdge(x,i); } ll ans = 0; for(int i=1;i<=n;i++) { if(vis[i]) continue; dfs(i,-1); ans += max(dp(edgeLeft,-1),dp(edgeRight,-1)); } printf("%lld\n",ans); return 0; }