题目背景:
Z国的骑士团是一个很有势力的组织,帮会中汇聚了来自各地的精英。他们劫富济贫,惩恶扬善,受到社会各界的赞扬。
最近发生了一件可怕的事情,邪恶的Y国发动了一场针对Z国的侵略战争。战火绵延五百里,在和平环境中安逸了数百年的Z国又怎能抵挡的住Y国的军队。于是人们把所有的希望都寄托在了骑士团的身上,就像期待有一个真龙天子的降生,带领正义打败邪恶。
骑士团是肯定具有打败邪恶势力的能力的,但是骑士们互相之间往往有一些矛盾。每个骑士都有且仅有一个自己最厌恶的骑士(当然不是他自己),他是绝对不会与自己最厌恶的人一同出征的。
战火绵延,人民生灵涂炭,组织起一个骑士军团加入战斗刻不容缓!国王交给了你一个艰巨的任务,从所有的骑士中选出一个骑士军团,使得军团内没有矛盾的两人(不存在一个骑士与他最痛恨的人一同被选入骑士军团的情况),并且,使得这支骑士军团最具有战斗力。
为了描述战斗力,我们将骑士按照1至N编号,给每名骑士一个战斗力的估计,一个军团的战斗力为所有骑士的战斗力总和。(N<=1,000,000,骑士战斗力为不超过1,000,000的正整数)。
输入格式:
第一行包括一个正整数N,描述骑士团的人数。
接下来N行,每行两个正整数,按顺序描述每一名骑士的战斗力和他最痛恨的骑士。
题解:
根据题目的描述,每一个连通块里面有且仅有一个环,这些连通块就构成了一个基环树森林于是我们的思路就是,通过dfs找出环,然后对于环上的每一个骑士,我们以他为根,在树上进行dp(当然这个树上不包括环上其他骑士),找出满足要求的最大战斗力,然后再在环上进行dp,环上相邻的两个骑士不能同时出现在骑士团里。
图解:
在树上dp,我们设f [ i ][ 0 ]表示不选根节点 i 时能获得的最大战斗力,f [ i ][ 1 ]表示选根节点 i 时能获得的最大战斗力。
于是状态转移方程为:
f[i][0]=Σmax(f[j][0],f[j][1]) (j表示i的儿子,下同) f[i][1]=Σf[j][0] + w[i](w[i]表示i的战斗力)
在环上dp是我们要注意,因为是环,所以第一个骑士和最后一个骑士是相邻的,我们解决这个问题的方法是强制选第一个骑士或者最后一个骑士,然后取最大值。
设g [ i ][ 0] 表示不选第 i 个骑士的最大战斗力,g [ i ][ 1]表示选第i个骑士的最大战斗力。
于是状态转移方程为:
g[i][0]=max(g[i-1][0],g[i-1][1])+f[i][0](f的含义与上面一样,下同) g[i][1]=g[i-1][0]+f[i][1]
然后取max累加到答案中即可;
附上代码:
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=1000000; 5 int ver[2*N+5],nxt[2*N+5],head[N+5],v[N+5],v2[N+5],c[N+5];//c数组储存环上的节点 6 ll w[N+5],f[N+5][2],g[N+5][2],ans1,ans2; 7 int n,cnt,tot,st;//由于存节点时tot是累加起来的,所以用st来表示环开始的节点的位置在c中编号 8 //我习惯加无向边。 9 void add(int x,int y){ 10 ver[++cnt]=y,nxt[cnt]=head[x],head[x]=cnt; 11 } 12 //dfs找出环 13 bool dfs(int x,int e){ 14 if(v[x]==1){//找到环的“衔接点” 15 v[x]=2,v2[x]=1,c[++tot]=x; 16 return 1; 17 } 18 v[x]=1; 19 for(int i=head[x];i;i=nxt[i]){ 20 if(i!=((e-1)^1)+1 && dfs(ver[i],i)){//邻接表成对储存;如果ver[i]在环上的话 21 if(v[x]!=2){//不是衔接点 22 c[++tot]=x,v2[x]=1;//存入c数组,打上标记 23 return 1; 24 }else{ 25 return 0; 26 } 27 } 28 } 29 return 0; 30 } 31 //树上进行dp 32 void tree_dp(int x){ 33 f[x][0]=0,f[x][1]=w[x]; 34 for(int i=head[x];i;i=nxt[i]){ 35 int y=ver[i]; 36 if(v2[y]) continue;//防止找到环上其他点 37 v2[y]=1;//把基环树上所有点都打上v2标记,标记这颗基环树已经跑过 38 tree_dp(y); 39 f[x][0]+=max(f[y][0],f[y][1]); 40 f[x][1]+=f[y][0]; 41 } 42 } 43 44 int main(){ 45 scanf("%d",&n); 46 for(int i=1;i<=n;++i){ 47 scanf("%lld",&w[i]); 48 int y; 49 scanf("%d",&y); 50 add(i,y),add(y,i); 51 } 52 for(int i=1;i<=n;++i){ 53 if(!v2[i]){//如果还没跑过这颗基环树 54 st=tot+1; 55 dfs(i,0);//找出基环树上的环; 56 for(int j=st;j<=tot;++j) tree_dp(c[j]);//对环上点进行dp 57 memset(g,0,sizeof(g)); 58 g[st][0]=g[st][1]=f[c[st]][0];//强制环上第一个骑士不选 59 for(int j=st+1;j<=tot;++j){ 60 g[j][0]=max(g[j-1][0],g[j-1][1])+f[c[j]][0]; 61 g[j][1]=g[j-1][0]+f[c[j]][1]; 62 } 63 ans1=max(g[tot][0],g[tot][1]); 64 memset(g,0,sizeof(g)); 65 g[st][0]=g[st][1]=f[c[st]][1],f[c[st+1]][1]=f[c[st+1]][0];//强制第一个选 66 for(int j=st+1;j<=tot-1;++j){ 67 g[j][0]=max(g[j-1][0],g[j-1][1])+f[c[j]][0]; 68 g[j][1]=g[j-1][0]+f[c[j]][1]; 69 } 70 ans1=max(ans1,max(g[tot-1][0],g[tot-1][1])+f[c[tot]][0]); 71 ans2+=ans1;//对每一颗基环树累加答案 72 } 73 } 74 printf("%lld",ans2); 75 return 0; 76 }