题意:给你一个有向图,一共N个顶点,且每个顶点只有一个前驱或后继,在顶点上建立圣地,那么就可以获得一个信仰值,如果在这个顶点的后继节点上也建立圣地,那么将改变一定的信仰值,求解能获取的最大信仰值。
分析:比赛时一点思路也没有。然后看啦一下别人博客,发现也不是太难,要注意一个关键点,这个有向图的所有子图均为点数等于边数的简单图。由此可以推出每个子图有且仅有一个环。环上可能有一些树枝。用拓扑定理和树形DP先处理树枝。然后在处理每个环。对于环,从环上任意一点开始,分在这一点建圣庙,和不建。树形DP处理。
这里附上代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int INF=0x3f3f3f3f;
typedef long long ll;
ll dp[100100][2],dp1[100100][2],p[100100],g[100100];
int f[100100],n,in[100100],t[100100],n2;
bool vis[100100];
int main()
{
while(scanf("%d",&n)!=EOF)
{
memset(in,0,sizeof(in));
memset(vis,false,sizeof(vis));
for(int i=1;i<=n;i++)
{
scanf("%lld%lld%d",&p[i],&g[i],&f[i]);
in[f[i]]++;
}
queue<int> que;
for(int i=1;i<=n;i++)
{
dp[i][1]=p[i];
dp[i][0]=0;
if(in[i]==0)
{
que.push(i);
}
}
while(!que.empty())
{
int from=que.front();que.pop();
int to=f[from];
in[to]--;
if(in[to]==0)
{
que.push(to);
}
dp[to][1]+=max(dp[from][0],dp[from][1]+g[from]);
dp[to][0]+=max(dp[from][0],dp[from][1]);
vis[from]=true;
}
ll ans=0;
for(int i=1;i<=n;i++)
{
if(vis[i])continue;
int from=i;
t[1]=i;n2=1;
vis[from]=true;
while(f[from]!=i)
{
// cout<<from<<"--->";
from=f[from];
vis[from]=true;
t[++n2]=from;
}
//cout<<endl;
memcpy(dp1,dp,sizeof(dp));
dp[t[2]][1]+=(dp[t[1]][1]+g[t[1]]);
dp[t[2]][0]+=dp[t[1]][1];
for(int j=3;j<=n2;j++)
{
dp[t[j]][1]+=max(dp[t[j-1]][0],dp[t[j-1]][1]+g[t[j-1]]);
dp[t[j]][0]+=max(dp[t[j-1]][0],dp[t[j-1]][1]);
}
dp1[t[2]][1]+=(dp1[t[1]][0]);
dp1[t[2]][0]+=(dp1[t[1]][0]);
for(int j=3;j<=n2;j++)
{
dp1[t[j]][1]+=max(dp1[t[j-1]][0],dp1[t[j-1]][1]+g[t[j-1]]);
dp1[t[j]][0]+=max(dp1[t[j-1]][0],dp1[t[j-1]][1]);
}
int to=t[n2];
ans+=max(max(dp[to][1]+g[to],dp[to][0]),max(dp1[to][1],dp1[to][0]));
// cout<<ans<<endl;
}
printf("%lld\n",ans);
}
return 0;
}