ZJOI2008骑士(Bzoj1040)——树形DP(基环树)
Posted gosick
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ZJOI2008骑士(Bzoj1040)——树形DP(基环树)相关的知识,希望对你有一定的参考价值。
Description
Z国的骑士团是一个很有势力的组织,帮会中汇聚了来自各地的精英。他们劫富济贫,惩恶扬善,受到社会各
界的赞扬。最近发生了一件可怕的事情,邪恶的Y国发动了一场针对Z国的侵略战争。战火绵延五百里,在和平环境
中安逸了数百年的Z国又怎能抵挡的住Y国的军队。于是人们把所有的希望都寄托在了骑士团的身上,就像期待有一
个真龙天子的降生,带领正义打败邪恶。骑士团是肯定具有打败邪恶势力的能力的,但是骑士们互相之间往往有一
些矛盾。每个骑士都有且仅有一个自己最厌恶的骑士(当然不是他自己),他是绝对不会与自己最厌恶的人一同出
征的。战火绵延,人民生灵涂炭,组织起一个骑士军团加入战斗刻不容缓!国王交给了你一个艰巨的任务,从所有
的骑士中选出一个骑士军团,使得军团内没有矛盾的两人(不存在一个骑士与他最痛恨的人一同被选入骑士军团的
情况),并且,使得这支骑士军团最具有战斗力。为了描述战斗力,我们将骑士按照1至N编号,给每名骑士一个战
斗力的估计,一个军团的战斗力为所有骑士的战斗力总和。
Input
第一行包含一个正整数N,描述骑士团的人数。接下来N行,每行两个正整数,按顺序描述每一名骑士的战斗力
和他最痛恨的骑士。
Output
应包含一行,包含一个整数,表示你所选出的骑士军团的战斗力。
Sample Input
10 2
20 3
30 1
Sample Output
HINT
N ≤ 1 000 000,每名骑士的战斗力都是不大于 1 000 000的正整数。
Solution
好久没写博客了,就来写个这道树形DP的题解吧。
题目大意是选出战斗力总和最高的骑士
团,但是每个骑士都有一个他认为长得丑的骑士,于是他们就不能在一起了~~
好了不扯了。首先骑士a认为骑士b长得丑,那么选了b就不能选a,同样选了a也不能选b了,也就是说a、b只能选一个,那么就要连无向边而不是有向边了。
如果没有环的话,这道题就是一个简单的树形DP,问题是有环。稍加分析就会发现,树上有且只有一个环(这好像是是叫环套树或基环树)。对于有环的问题,一般是断环为链,随便找条环上的边删去即可。那么我们就要考虑删去边后如何保留这条边原来的影响,其实一条边就表示一个限制条件,假设删去边(u,v),可以以u为根但不选u跑一遍DP,再以v为根但不选v跑一遍DP,取两者的的最大值就好啦~ 有人可能会问为什么可以随便找一条环上的边删去,因为我们其实只是把一条边转换为了直接的条件,让有环图转换为了无环图,所以原图的所有性质其实是不变的。
还有一个问题,原题没有保证图联通,所以这其实是一个基环树森林(真du liu),所以要把每棵树上的最大值加起来才是最终答案。
DP的话用f[x][0]和f[x][1]分别表示x选与不选,很容易得到状态转移方程:
f[x][0]+=max(f[y][0],f[y][1]);(x不选,则y可选可不选,取两者最大值)
f[x][1]+=f[y][0];(x选,则y一定不可选)
其中y∈x的儿子集。
找环的话笔者用的是并查集。好了,去吧代码菌:
#include<cstdio> #include<cstring> #include<cctype> #include<utility> #include<algorithm> using namespace std; const int N=1e6+5; int n,m,sco[N],father[N]; long long ans,t,f[N][2]; pair<int,int> cut[N]; struct Graph{ struct edge{ int v,last; }e[N<<1]; int tot,tail[N]; inline void add(int x,int y){ e[++tot]=(edge){y,tail[x]}; tail[x]=tot; } }G; inline int find(int x){return father[x]==x?x:father[x]=find(father[x]);} inline int read() { int X=0,w=0; char ch=0; while(!isdigit(ch)) {w|=ch==‘-‘;ch=getchar();} while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar(); return w?-X:X; } void DP(int x,int fa){ register int p,y; f[x][1]=(long long)sco[x],f[x][0]=0; for(p=G.tail[x];p;p=G.e[p].last){ y=G.e[p].v; if(y==fa) continue; //防止走重边 DP(y,x); f[x][0]+=max(f[y][0],f[y][1]); f[x][1]+=f[y][0]; } } int main() { register int i,y; n=read(); for(i=1;i<=n;++i) father[i]=i; for(i=1;i<=n;++i){ sco[i]=read(),y=read(); if(find(i)!=find(y)) G.add(i,y),G.add(y,i),father[father[y]]=father[i]; else cut[++m].first=i,cut[m].second=y; //cut存要删去的边的起点和终点 } for(i=1;i<=m;++i){ DP(cut[i].first,0); t=f[cut[i].first][0]; //不选起点,因此第二下标只考虑0,下面同理 DP(cut[i].second,0); t=max(t,f[cut[i].second][0]); ans+=t; //把每棵树上的最大值加起来 } printf("%lld",ans); return 0; }
希望能帮到大家,请多多指教.
2018-09-02
以上是关于ZJOI2008骑士(Bzoj1040)——树形DP(基环树)的主要内容,如果未能解决你的问题,请参考以下文章