题意:
有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;
}