题意:n个点,每个点有一个faith值,然后有一个关联的点。如果在i点和它的关联点pi同时修
神庙则得到的faith值变为fi+gi,如果只在i点修不在pi修建,则得到的faith值为fi。问最优修建方案
中能获得的faith值即求能获得的最大faith值。
算法:
由于有n个点和n条边,所以一定是一个带环的图,环外有小尾巴。
对于环外的点,利用dp[i][0]+=max(dp[soni][0],dp[soni][1])和dp[i][1]+=max(dp[soni][0],dp[soni][1]+g[son])
不断向上缩点。
对于每个环,则枚举其中一个点的状态,之后就成了一条链,又可以运用上面的方程。
这个选与不选问题的dp方程是树形dp里比较基本的类型。如poj 2342 Anniversary party
和 POJ 1463 && HDU 1054 Strategic Game
这题比较新颖在于图中环的处理,枚举环中一个点来使环变为链。
我没想出这个枚举的方法,参考了下面的blog。
http://blog.csdn.net/ahero_happy/article/details/6728471
#include<cstdio> #include<iostream> #include<cstring> #define ll long long #define maxn 100010 using namespace std; int in[maxn],q[maxn],p[maxn],f[maxn],g[maxn]; ll dp[maxn][2],dp1[maxn][2]; bool vis[maxn]; ll maxx(ll a,ll b) { return a>b?a:b; } ll dfs1(int st,int cur,ll dp[][2],int flag) { int k=cur; int i=p[k]; while(i!=st) { dp[i][0]+=maxx(dp[k][0],dp[k][1]); dp[i][1]+=maxx(dp[k][0],dp[k][1]+g[k]); k=i; i=p[k]; } if(flag) dp[k][1]+=g[k]; return maxx(dp[k][0],dp[k][1]); } ll dfs(int now) { int k=now; int i=p[k]; dp1[i][0]+=dp[k][0]; dp1[i][1]+=dp[k][0];//now不取 ll a1=dfs1(k,i,dp1,0); dp[i][0]+=dp[k][1]; dp[i][1]+=dp[k][1]+g[k];//now取 ll a2=dfs1(k,i,dp,1); return maxx(a1,a2); } int main() { int n,rear; ll sum; while(scanf("%d",&n)!=EOF) { memset(in,0,sizeof(in)); memset(vis,0,sizeof(vis)); memset(dp,0,sizeof(dp)); for(int i=1;i<=n;i++) { scanf("%d%d%d",&f[i],&g[i],&p[i]); in[p[i]]++; } rear=0; for(int i=1;i<=n;i++) { dp[i][1]=f[i]; if(!in[i]) q[rear++]=i; } while(rear) { int k = q[--rear]; vis[k] = true; int i = p[k]; dp[i][0]+=maxx(dp[k][0],dp[k][1]); dp[i][1]+=maxx(dp[k][0],dp[k][1]+g[k]); if((--in[i])==0) q[rear++]=i; } memcpy(dp1,dp,sizeof(dp)); sum=0; for(int i=1;i<=n;i++) { if(!vis[i]) { sum+=dfs(i); vis[i]=true; for(int j=p[i];j!=i;j=p[j]) vis[j]=true; } } printf("%lld\n",sum); } return 0; }
zoj 3527 Shinryaku! Kero Musume (树形dp---带尾巴的环的处理)
原文:http://blog.csdn.net/u012841845/article/details/19292505