[ZJOI2008]骑士 题解

题目背景:

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 }

时间复杂度大概是o(n)吧,因为每一个点都只遍历一次。

猜你喜欢

转载自www.cnblogs.com/Asika3912333/p/11329359.html