洛谷2607 bzoj1040 ZJOI2008 骑士 基环树dp

17 篇文章 0 订阅
2 篇文章 0 订阅

题目链接
题意:给你 n n n个点和 n n n条有向边,每个点有点权,保证每个点只有出度都是 1 1 1,不保证连通,保证没有自环,要求点 x x x所能到达的点和能到达点 x x x的点不能同时被选,选出若干个点,使他们的权值和最大。
题解:
我们发现每个点只有一条出边,那么有点像一棵树(每个点最多只有一个父节点),由于不一定连通,所以实际应该说是一个由若干基环树组成的森林,因为每个连通块都不会存在一个没有出度的点成为根,所以根一定是一个环。但是和树的区别是可能会有环。但是我们发现只会存在简单环,不会出现环套环,原因是每个点的出度是 1 1 1,要形成环套环的话一定会在环上有某个点的出度大于 1 1 1。由此我们考虑一下可以发现,对于每个连通块,环只可能出现在该连通块的“根”,也就是连通块只有根的地方可能是环。这里的连通块不是有向图中tarjan意义上能互相到达的连通,而是把有向边看作无向边之后的连通情况。只有根能出现环的原因是,假如有环并且环不是该连通块的根,那么环上一定有一个点的父节点不在环上,又因为形成了一个环,所以这个节点一定还有一个节点在环上,这与每个点只有一个出度矛盾,所以证明了有环的话环一定出现在连通块的根上。
其实拿到这道题后不难发现,假如这道题没有环,那么就变成了那道叫anniversary party(没有上司的舞会)的题了。我们可以设 d p [ x ] [ 0 ] dp[x][0] dp[x][0]为不选 x x x点的最大权值和, d p [ x ] [ 1 ] dp[x][1] dp[x][1]是选 x x x点的最大权值和,那么转移方程是 d p [ x ] [ 0 ] = ∑ y ∈ s o n [ x ] m a x ( d p [ y ] [ 0 ] , d p [ y ] [ 1 ] ) dp[x][0]=\sum_{y\in son[x]}max(dp[y][0],dp[y][1]) dp[x][0]=yson[x]max(dp[y][0],dp[y][1]) d p [ x ] [ 1 ] = v a l [ x ] + ∑ y ∈ s o n [ x ] d p [ y ] [ 0 ] dp[x][1]=val[x]+\sum_{y\in son[x]}dp[y][0] dp[x][1]=val[x]+yson[x]dp[y][0]
简单解释一下,就是根据题目要求,假如不选点 x x x,那么 x x x的儿子选不选都可以;假如选了点 x x x,那么 x x x的儿子一定不能同时选。
那么现在我们就来解决有环的问题。我们对于一个没有访问过的点,不断地找它的父节点,并标记为访问过的点,那么最后一定会到达这个连通块的根,也就是环上的一个点。我们知道,遇到环的问题时一个常用的办法是破环为链,那么我们对于每一个连通块,找到环上一个点,我们把这个点与其父节点的边断开,这样就形成了一棵树。然后分别把这个点和它断开前的父节点分别作为树的根进行上述的树形dp,在dp过程中这两个点强制只能选一个,然后对两种情况取一个最大值作为这个连通块最大权值和。
这样我们可以求出每一个连通块的最大权值和,那么最终答案就是每个连通块答案的和,因为连通块之间是没有任何影响的。
代码:

#include <bits/stdc++.h>
using namespace std;

int n,hed[1000010],cnt,rt,vis[1000010],fa[1000010];
long long dp[1000010][2],ans,val[1000100];
struct node
{
    int to,next;
}a[1000010];
inline void add(int from,int to)
{
    a[++cnt].to=to;
    a[cnt].next=hed[from];
    hed[from]=cnt;
}
inline void dfs(int x)
{
    vis[x]=1;
    dp[x][0]=0;
    dp[x][1]=val[x];
    for(int i=hed[x];i;i=a[i].next)
    {
        int y=a[i].to;
        if(y!=rt)
        {
            dfs(y);
            dp[x][0]+=max(dp[y][0],dp[y][1]);
            dp[x][1]+=dp[y][0];
        }
        else
        dp[y][1]-=2e9;//强制不选当前rt存的点 
    }
}
inline void find(int x)
{	
    rt=x;
    vis[x]=1;
    while(!vis[fa[rt]])
    {
        rt=fa[rt];
        vis[rt]=1;
    }
    dfs(rt);
    long long ji=max(dp[rt][0],dp[rt][1]);
    vis[rt]=1;
    rt=fa[rt];
    dfs(rt);
    ans+=max(ji,max(dp[rt][0],dp[rt][1]));
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
    {
        int x;
        scanf("%lld%d",&val[i],&x);
        fa[i]=x;
        add(x,i);
    }
    for(int i=1;i<=n;++i)
    {
        if(!vis[i])
        find(i);
    }
    printf("%lld\n",ans);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值