ZOJ 3527 Shinryaku! Kero Musume 【树形DP[带简单环]】

本文介绍了一道关于有向图的算法题,通过拓扑排序和动态规划解决如何在图中选择节点以获得最大收益的问题。适用于理解图论算法及其实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原题链接

题意:给你一个有向图,一共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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值