[ZJOI2008] 骑士(基环树dp)

[ZJOI2008] 骑士

题目描述

Z 国的骑士团是一个很有势力的组织,帮会中汇聚了来自各地的精英。他们劫富济贫,惩恶扬善,受到社会各界的赞扬。

最近发生了一件可怕的事情,邪恶的 Y 国发动了一场针对 Z 国的侵略战争。战火绵延五百里,在和平环境中安逸了数百年的 Z 国又怎能抵挡的住 Y 国的军队。于是人们把所有的希望都寄托在了骑士团的身上,就像期待有一个真龙天子的降生,带领正义打败邪恶。

骑士团是肯定具有打败邪恶势力的能力的,但是骑士们互相之间往往有一些矛盾。每个骑士都有且仅有一个自己最厌恶的骑士(当然不是他自己),他是绝对不会与自己最厌恶的人一同出征的。

战火绵延,人民生灵涂炭,组织起一个骑士军团加入战斗刻不容缓!国王交给了你一个艰巨的任务,从所有的骑士中选出一个骑士军团,使得军团内没有矛盾的两人(不存在一个骑士与他最痛恨的人一同被选入骑士军团的情况),并且,使得这支骑士军团最具有战斗力。

为了描述战斗力,我们将骑士按照 1 1 1 n n n 编号,给每名骑士一个战斗力的估计,一个军团的战斗力为所有骑士的战斗力总和。

输入格式

第一行包含一个整数 n n n,描述骑士团的人数。

接下来 n n n 行,每行两个整数,按顺序描述每一名骑士的战斗力和他最痛恨的骑士。

输出格式

应输出一行,包含一个整数,表示你所选出的骑士军团的战斗力。

样例 #1

样例输入 #1

3
10 2
20 3
30 1

样例输出 #1

30

提示

数据规模与约定

对于 30 % 30\% 30% 的测试数据,满足 n ≤ 10 n \le 10 n10

对于 60 % 60\% 60% 的测试数据,满足 n ≤ 100 n \le 100 n100

对于 80 % 80\% 80% 的测试数据,满足 n ≤ 1 0 4 n \le 10 ^4 n104

对于 100 % 100\% 100% 的测试数据,满足 1 ≤ n ≤ 1 0 6 1\le n \le 10^6 1n106,每名骑士的战斗力都是不大于 1 0 6 10^6 106 的正整数。

思路

这道题就是很典型的基环树,为什么是基环树呢?因为一个点对应一条边(因为不存在没有矛盾的两人)。

基环树dp的操作:它相比于仙人掌树,它的环只有一个,此时我们可以用栈来找它的环,而仙人掌树必须是用targin的改进版(无需缩点的)。

然后基环树有环,因此得破一个点,然后我们就可以操作了:

我们设 f[u][0/1] 表示以 u 为根节点的子树中,根节点选或不选的方案数集合
状态转移就跟 没有上司的舞会 那道题类似。

具体代码:

代码

//基环树dp
//f[u][0/1]表示以i的子树中,0表示选,1表示不选的最大战斗力
#include<iostream>
#include<algorithm>
#include<cstring>

#define int long long

using namespace std;

const int N = 1e6+10,M = 2*N,INT = 1e18;

int e[M],ne[M],h[N],idx;
int w[N];
bool ins[N],st[N];
int stk[N];//基环树是用栈来实现找环的
int f1[N][2],f2[N][2];//f1为删除前,f2为删后
int n;
int rm[N],ans;

void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

void dfs_f(int u,int ap,int f[][2]){
    //no u
    for(int i=h[u];~i;i=ne[i]){
        if(rm[i])continue;
        int j=e[i];
        dfs_f(j,ap,f);
        f[u][0]+=max(f[j][0],f[j][1]);
    }
    // yes u
    f[u][1]=-INT;
    
    if(u!=ap){
        f[u][1]=w[u];
        
        for(int i=h[u];~i;i=ne[i]){
            if(rm[i])continue;
            int j=e[i];
            f[u][1]+=f[j][0];
        }
    }
    
}

void dfs_c(int u,int from){
    st[u]=ins[u]=true;
    
    for(int i=h[u];~i;i=ne[i]){
        int j=e[i];
        if(!st[j])dfs_c(j,i);//继续找环
        else if(ins[j]){
            rm[i]=1;//将 u -> j 的边删掉
            dfs_f(j,-1,f1);//j不选
            dfs_f(j,u,f2);//j选
            ans+=max(f1[j][0],f2[j][1]);
        }
        
    }
    ins[u]=false;
}

signed main(){
    cin>>n;
    
    memset(h,-1,sizeof h);
    
    for(int i=1;i<=n;i++){
        int x;
        cin>>w[i]>>x;
        add(x,i);
    }
    
    for(int i=1;i<=n;i++){
        if(!st[i]){
            dfs_c(i,-1);
        }
    }
    
    cout<<ans;
    
    return 0;
    
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

green qwq

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值