题意: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.youkuaiyun.com/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;
}