http://www.lydsy.com/JudgeOnline/problem.php?id=1040
http://codevs.cn/problem/1423/
前言:这是第一次发bzoj题解,纪念一下。
noip在即,大家都要加油哦。
题意:有N个点,对于每个点都给出另一个点,表示它们之间有一条无向边。求最大带权独立子集。
这整个图不一定联通,但是每一个联通子图都有一个性质:边数与点数对应相等。每个联通子图可以分开处理。
最大(带权)独立子集是NP-hard问题,但是由于这里图的特殊性,可以用动态规划来解决。
对于一棵树的最大(带权)独立子集,任选一点为根,令f[i][0]和f[i][1]分别表示第i个点不选和选,该子树能取得的最大权值和。
则f[i][0]=sigma{max(f[j][0],f[j][1])},f[i][1]=w[i]+sigma{f[j][0]},其中w[i]为第i个点的权值,j是i的儿子。
对于一个边数和点数的联通图,可以考虑删去适当的一条边,使其成为一棵树。
设删掉的边的两点为x和y,分别令它们为根做树形DP,因为两者最多取其一,所以取f[x][0]和f[y][0]的较大值计入该联通子图的贡献。
代码:(拆边写的很难看,并且因为这个WA了三次,每次都90分,分别错的是第6,5,9个数据点0、
#include<cstdio> #include<iostream> #include<cstring> #define rpt(i,l,r) for(i=l;i<=r;i++) #define rpd(i,r,l) for(i=r;i>=l;i--) #define maxn 10000005 #define mx(a,b) (a)>(b)?(a):(b) using namespace std; int l[maxn],w[maxn],s[maxn]={0},t[maxn<<1],b[maxn]={0},fa[maxn]; int n,i,kk,x,y; long long f[maxn][2]; long long p,q,res=0; void findxy(int e){ int i; rpt(i,s[e-1]+1,s[e]) if(b[t[i]]==0&&t[i]){ b[t[i]]=1; fa[t[i]]=e; int tt=t[i]; findxy(t[i]); fa[tt]=0; } else if((fa[e]!=t[i]||(l[l[t[i]]]==t[i]&&l[l[e]]==e))&&kk){ kk=0; x=e; y=t[i]; int j; rpt(j,s[t[i]-1]+1,s[t[i]]) if(t[j]==e){ t[j]=0; break; } t[i]=0; } } void dp(int e){ int i; f[e][0]=0; f[e][1]=w[e]; rpt(i,s[e-1]+1,s[e]) if(t[i]&&fa[e]!=t[i]){ fa[t[i]]=e; dp(t[i]); f[e][0]+=mx(f[t[i]][0],f[t[i]][1]); f[e][1]+=f[t[i]][0]; fa[t[i]]=0; } } int main(){ scanf("%d",&n); rpt(i,1,n){ scanf("%d%d",&w[i],&l[i]); s[i]++; s[l[i]]++; } rpt(i,1,n) s[i]+=s[i-1]; rpd(i,n,1) s[i]=s[i-1]; rpt(i,1,n){ t[++s[i]]=l[i]; t[++s[l[i]]]=i; } rpt(i,1,n) if(b[i]==0){ kk=1; b[i]=1; findxy(i); dp(x); p=f[x][0]; dp(y); q=f[y][0]; res+=mx(p,q); } printf("%lld\n",res); }