P2607 [ZJOI2008]骑士(基环树+树形dp,断环二次dp法)

题意:

有n个骑士,每个骑士都有一个战斗力,
此外每个骑士有一个讨厌的骑士,不可以同时上场,
保证讨厌的骑士不是自己,
问选出的战斗力和最大是多少

数据范围:n<=1e6

解法:
每个人有且只有一个最讨厌的人,而且不会讨厌自己,
如果抽象为有向图,那么没有自环,
因为出度都为1,总出度为n,图中每个连通块内有且只有一个环,
那么整个图就是基环内向树森林.

由于题目中与讨厌的骑士不能同时上场,
可以每棵基环内向树当作无向图基环树,
问题就变为计算树的最大点权独立集,

考虑环上任意一条边,显然两端点至少有一个不选,
将这条边断开,以两端点为根分别进行树形dp计算最大独立集,
假设两端点为x和v,如果选择x,d[x][1]可能包含v,显然非法,
实际上考虑两端至少有一个不选,直接对d[x][0]和d[v][0]取max就是答案.

计算每颗基环树的答案,累加即可.

需要注意的点:
环虽然不是自环,但是可能是x->v,v->x,
这种情况下标记删除的边就不能用两个点标记了,
需要用边的id标记,否则会导致树断开,不连通,答案就错了.
code:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxm=1e6+5;
int head[maxm],nt[maxm<<1],to[maxm<<1],w[maxm<<1],cnt;
bool mark[maxm];
ll d[maxm][2];
int a[maxm];
int b[maxm];
int id1[maxm],id2[maxm];
int delx,dely;
int n;
void add(int x,int y){
    cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;
}
void dfs1(int x){//dfs找环(注意并没有将基环树上的点全部标记)
    mark[x]=1;
    if(!mark[b[x]]){
        dfs1(b[x]);
    }else{//x和b[x]就是环上的一条边
        delx=x,dely=b[x];
    }
}
void dfs(int x,int fa){
    mark[x]=1;//这里记得标记,因为找环的时候没有全部标记
    d[x][0]=0;
    d[x][1]=a[x];
    for(int i=head[x];i;i=nt[i]){
        int v=to[i];
        if(v==fa)continue;
        if(i==id1[delx]||i==id2[delx])continue;//用边的id判断
        dfs(v,x);
        d[x][0]+=max(d[v][0],d[v][1]);
        d[x][1]+=d[v][0];
    }
}
signed main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d%d",&a[i],&b[i]);
        add(i,b[i]);
        add(b[i],i);
        id1[i]=cnt-1;//记录边的id
        id2[i]=cnt;
    }
    ll ans=0;
    for(int i=1;i<=n;i++){
        if(mark[i])continue;
        dfs1(i);
        ll temp=0;
        dfs(delx,-1);
        temp=max(temp,d[delx][0]);
        dfs(dely,-1);
        temp=max(temp,d[dely][0]);
        ans+=temp;
    }
    cout<<ans<<endl;
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值