【bzoj1040】骑士[ZJOI2008](树形dp)
题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=1040
这道题,很明显根据仇恨关系构造出的图形是一堆环套树。如果是普通的树,就可以马上裸树形dp了,于是我们先解决这个环的问题。所以要先把环找出来。
找出环后,假如断掉一条环边,环套树就变成了普通的树,而我们可以直接对这棵树进行dp,但是要控制被断掉的边的两端不被选择。
对断边形成的树进行dp的时候,我们的dp方程是这样表示的:f[i][0/1]表示结点i不选/选。
假设断掉的两条边两端的结点是x和y,然后我们可以发现:当以x为根时,f[x][0]包含了不选x,选/不选y的情况;而当以y为根时,f[y][0]包含了不选y,选/不选x的情况。把这两种情况取个max,刚好就绕过了既选x,又选y的情况。
然后把每棵环套树的答案加起来即可。
PS:QAQ……细节调了快两个小时……
代码:
#include<cstdio> #include<cstring> #include<cmath> #include<cstdlib> #include<ctime> #include<algorithm> #include<queue> #include<vector> #include<map> #define ll long long #define min(a,b) (a<b?a:b) #define max(a,b) (a>b?a:b) ll read() { ll tmp=0; char f=1,c=getchar(); while(c<'0'||'9'<c){if(c=='-')f=-1; c=getchar();} while('0'<=c&&c<='9'){tmp=tmp*10+c-'0'; c=getchar();} return tmp*f; } using namespace std; int fir[1000010],to[2000010],ne[2000010]; int a[1000010]; bool vis[1000010]; ll f[1000010][2]; int n,root,del,tot=0; ll ans=0; void add(int x,int y){to[tot]=y; ne[tot]=fir[x]; fir[x]=tot++;} void dfs(int now,int pa) { vis[now]=1; for(int i=fir[now];i>=0;i=ne[i]) if(i!=(pa^1)){ if(vis[to[i]]){ root=to[i]; del=i^1; continue; } dfs(to[i],i); } } void dp(int now,int pa) { f[now][1]=a[now]; f[now][0]=0; for(int i=fir[now];i>=0;i=ne[i]) if(i!=(pa^1)&&i!=del&&i!=(del^1)){ dp(to[i],i); f[now][1]+=f[to[i]][0]; f[now][0]+=max(f[to[i]][0],f[to[i]][1]); } } void work() { dfs(root,-1); dp(root,-1); ll tmp=f[root][0]; dp(to[del],-1); ans+=max(tmp,f[to[del]][0]); } int main() { n=read(); for(int i=1;i<=n;i++)fir[i]=-1; for(int i=1;i<=n;i++){ int x=read(),y=read(); a[i]=x; add(i,y); add(y,i); } for(int i=1;i<=n;i++) if(!vis[i])root=i,work(); printf("%lld",ans); }
总结:遇到环套树的题,或者先断掉一条环边处理树,再把边的情况考虑进去;或者先处理环上的子树,再考虑整个环。