ZOJ 3527 树形DP(章鱼图DP)

题意

有N个村庄,每个村庄有一定的信仰值,占领村庄可以得到信仰值,每个村庄有一个关联村庄,同时占领关联村庄可以得到加成(可能为负),问占领一些村庄,最多可以得到多少信仰值

题解

这个DP已经不能算是树形DP了,因为有环。不过有趣的是环只有一个(可能有多个环,但是不可能存在环套环)。这样的话我们便可以破环进行DP。
如果画一下图的话可以发现这个图很像一只章鱼,我们进行DP的话,可以首先从触手的位置开始,我们可以首先从触手尖端开始DP(触手尖端为入度为0的点),一直DP到环(环上的点即便减去触手入度也不为0)。对于环上的点,我们要分情况进行DP(因为DP只能用于有向无环图),我们可以将环剪开。对于剪开的位置,我们可以选择选,可以选择不选,于是我们可以开两个DP数组分别讨论。
最后再将两个数组合并,同时将最大信仰值增加到ANS上。需要注意的是,可能有多条章鱼,所以需要进行多次DP,求总和。

注意事项

ZOJ莫名其妙的内存分配机制。。Queue在主函数循环内声明直接TLE。。声明为全局变量2700ms,声明到函数里直接600MS。。终于明白BFS为什么要开一个函数去写了。。

代码

#include <iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<cmath>
#include<queue>
#include<string>
#include<set>
#include<map>
#include<bitset>
#include<stack>
#include<string>
#define UP(i,l,h) for(int i=l;i<h;i++)
#define DOWN(i,h,l) for(int i=h-1;i>=l;i--)
#define W(a) while(a)
#define MEM(a,b) memset(a,b,sizeof(a))
#define LL long long
#define INF 0x3f3f3f3f3f3f3f3f
#define MAXN 120050
#define MOD 1000000007
#define EPS 1e-3
#define int LL
using namespace std;
int f[MAXN],g[MAXN],xnext[MAXN],in[MAXN],dp[MAXN][2],dp1[MAXN][2],p[MAXN];
int num;
bool vis[MAXN];
queue<int> q;

void dfs(int u){
    p[++num]=u;
    vis[u]=true;
    int t=xnext[u];
    if(vis[t]) return ;
    dfs(t);
}

main() {
    int n;
    W(~scanf("%lld",&n)) {
        W(!q.empty()) q.pop();
        MEM(in,0);
        MEM(dp,0);
        MEM(dp1,0);
        MEM(vis,false);
        UP(i,1,n+1) {
            scanf("%lld%lld%lld",&f[i],&g[i],&xnext[i]);
            in[xnext[i]]++;
        }
        UP(i,1,n+1){
            dp[i][1]=f[i];
            if(in[i]==0){
                q.push(i);
                in[i]--;
            }
        }
        W(!q.empty()){
            int r=q.front();
            q.pop();
            vis[r]=true;
            int k=xnext[r];
            dp[k][0]+=max(dp[r][0],dp[r][1]);
            dp[k][1]+=max(dp[r][0],dp[r][1]+g[r]);
            if(--in[k]==0) q.push(k);
        }
        int ans=0;
        UP(j,1,n+1){
            if(!vis[j]){
                num=0;
                dfs(j);
                memcpy(dp1,dp,sizeof(dp));
                dp1[p[2]][0]+=dp1[p[1]][0];
                dp1[p[2]][1]+=dp1[p[1]][0];
                UP(i,3,num+1){
                    dp1[p[i]][0]+=max(dp1[p[i-1]][0],dp1[p[i-1]][1]);
                    dp1[p[i]][1]+=max(dp1[p[i-1]][0],dp1[p[i-1]][1]+g[p[i-1]]);
                }
                dp[p[2]][0]+=dp[p[1]][1];
                dp[p[2]][1]+=dp[p[1]][1]+g[p[1]];
                UP(i,3,num+1){
                    dp[p[i]][0]+=max(dp[p[i-1]][0],dp[p[i-1]][1]);
                    dp[p[i]][1]+=max(dp[p[i-1]][0],dp[p[i-1]][1]+g[p[i-1]]);
                }
                dp[p[num]][1]+=g[p[num]];
                int tmp=0;
                UP(i,1,num+1){
                    dp[p[i]][0]=max(dp[p[i]][0],dp1[p[i]][0]);
                    dp[p[i]][1]=max(dp[p[i]][1],dp1[p[i]][1]);
                    tmp=max(dp[p[i]][0],dp[p[i]][1]);
                }
                ans+=tmp;
            }
        }
        printf("%lld\n",ans);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值