树形dp(一)——骑士(Bzoj 1040)

题目链接:https://loj.ac/problem/10162
题目大意
每个骑士都有一个仇人,问要怎么选择骑士团成员,使成员不会有仇人在内,并且战斗力最大。
解题思路
如果这道题是一棵树,那么就是树的最大独立集的模板题,但是本题是一个有许多独立联通块的图,并且每个联通块必定是有且仅有一个环。为什么呢?因为骑士和仇人只会构成一条边,那就是一个点只会有一条边出去指向仇人,所以n个点会有n条边而且是同一联通块,那么必定是有且仅有一个环。知道这个之后,我们再看如果按照树的最大独立集的方法去做有什么问题。问题就是,在一个环上,如果以其中一个点为子树的根去dp,那么到了环上的最后一个点(即再指出便回到根),那么就可能会被选上。解决办法,就是选出环上的第一个点和最后一个点,分别作为根去dp,这样我们选择时,选择max(dp[U][0],dp[V][0])。dp[i][0]表示以i为子树的根不选i的最大值,dp[i][1]表示以i为子树的根选i的最大值。这样就能避免冲突。注意,我们再以V作为根去dp时会更新dp[U][0],所以要把之前的dp[U][0]保存下来,防止被覆盖。
AC代码

#include <iostream>
#include <stdio.h>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e6+5;
ll dp[maxn][2];//dp[i][0]以i为根的子图不选i最大值dp[i][1]以i为根的子图选i最大值
bool vis[maxn];
int n,cnt;
ll a[maxn];
int U,V,E;
struct node
{
    int to,next;
}edge[maxn<<1];
int head[maxn];
void add(int x,int y)
{
    edge[++cnt].to=y;
    edge[cnt].next=head[x];
    head[x]=cnt;
}
void dfs(int u,int fa)
{
    vis[u]=1;
    for(int i=head[u];~i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v==fa)
        continue;
        if(vis[v])
        {
            U=u;
            V=v;
            E=i;
            continue;
        }
        dfs(v,u);
    }
}
void solve(int u,int fa)
{
    dp[u][0]=0;
    dp[u][1]=a[u];
    for(int i=head[u];~i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v==fa||i==E||(i^1)==E)
        continue;
        solve(v,u);
        dp[u][0]+=max(dp[v][0],dp[v][1]);
        dp[u][1]+=dp[v][0];
    }
}
int main()
{
    memset(head,-1,sizeof(head));
    cnt=-1;
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
    {
        int x;
        scanf("%d%d",&a[i],&x);
        add(i,x);
        add(x,i);
    }
    ll ans=0;
    for(int i=1;i<=n;++i)
    {
        if(!vis[i])
        {
            dfs(i,-1);
            solve(U,-1);
            ll tmp=dp[U][0];//记录一下dp[U][0]之和后会被更新
            solve(V,-1);
            ans+=max(tmp,dp[V][0]);
        }
    }
    printf("%lld\n",ans);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值