每个骑士恨且仅恨一个人,不能与他共同出战,每个骑士还有一个武力值,问不冲突的最大出战武力值是多少?
关键词:基环森林(每棵树上有且仅有一个环)的最大权值独立集
建图时每个骑士指向他恨的人,该图的特点:仅有一条出边。
结论:每个点有且仅有一条出边的连通图的底图(有向边改为无向边)是基环树。
证明:引理:n个点构成无向连通图,则边数-点数+1=最小环数(每个环内无其他环)。该结论是经验结论,例如树有n-1个点,则无环,n点n边有一环。
对于本题,n个点引出n条边,有且仅有一个环形成。结论成立。
对于基环树,仍有O(n)的dp算法解决最大权值独立集的问题。
1.先找到树中的环,任选一条边,标记。
2.切掉该边,分别将该边的两端点作为根,进行树dp独立集寻找,取不含两端点的dp值的最大值即可。因为不含两端点则该边作用无效,可以切去该边。由于两者不能同时含有,分别不含两端点已经覆盖所有情况。
#include<stdio.h>
#include<iostream>
#include<string.h>
#define ll long long
#define maxn 1000010
#define INF 0x3f3f3f3f
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
int n;
ll w[maxn];
struct Edge{
int to,next;
}edge[maxn*2];
int head[maxn],tot;
int vis[maxn],root,_root,cut;//在找环时使用,判断是否遍历过
ll maxc[maxn][3],ans;
void add(int u,int v){
edge[tot].to=v,edge[tot].next=head[u],head[u]=tot++;
}
void find_onecircle(int u,int fa){//找环并顺便将所有的点标记
vis[u]=1;
for(int i=head[u];i!=-1;i=edge[i].next){
int v=edge[i].to;
if(v==fa) continue;
if(!vis[v]) find_onecircle(v,u);
else{ root=v,_root=u,cut=i; }
}
}
void tree_maxind(int u,int fa){
maxc[u][1]=w[u],maxc[u][0]=0;
for(int i=head[u];i!=-1;i=edge[i].next){
int v=edge[i].to;
if(i==cut||i==(cut^1)||v==fa) continue;
tree_maxind(v,u);
maxc[u][1]+=maxc[v][0];
maxc[u][0]+=max(maxc[v][0],maxc[v][1]);
}
}
ll onecircle_tree_maxind(int u){//在u所在基环树中求最大独立点集
ll tmp=0;
find_onecircle(u,-1);//在树中找到一个环,并切掉一条边
tree_maxind(root,-1);
tmp=maxc[root][0];
tree_maxind(_root,-1);
tmp=max(tmp,maxc[_root][0]);
return tmp;
}
int main(){
scanf("%d",&n);
mem(head,-1),ans=0,tot=0;
mem(vis,0),mem(maxc,0);
for(int i=1;i<=n;i++){
int a,b;
scanf("%lld%d",&a,&b);
w[i]=a;
add(i,b),add(b,i);
}
for(int i=1;i<=n;i++){
if(vis[i]) continue;
ans+=onecircle_tree_maxind(i);
}
printf("%lld\n",ans);
return 0;
}