Z国的骑士团是一个很有势力的组织,帮会中汇聚了来自各地的精英。他们劫富济贫,惩恶扬善,受到社会各
界的赞扬。最近发生了一件可怕的事情,邪恶的Y国发动了一场针对Z国的侵略战争。战火绵延五百里,在和平环境
中安逸了数百年的Z国又怎能抵挡的住Y国的军队。于是人们把所有的希望都寄托在了骑士团的身上,就像期待有一
个真龙天子的降生,带领正义打败邪恶。骑士团是肯定具有打败邪恶势力的能力的,但是骑士们互相之间往往有一
些矛盾。每个骑士都有且仅有一个自己最厌恶的骑士(当然不是他自己),他是绝对不会与自己最厌恶的人一同出
征的。战火绵延,人民生灵涂炭,组织起一个骑士军团加入战斗刻不容缓!国王交给了你一个艰巨的任务,从所有
的骑士中选出一个骑士军团,使得军团内没有矛盾的两人(不存在一个骑士与他最痛恨的人一同被选入骑士军团的
情况),并且,使得这支骑士军团最具有战斗力。为了描述战斗力,我们将骑士按照1至N编号,给每名骑士一个战
斗力的估计,一个军团的战斗力为所有骑士的战斗力总和。
第一行包含一个正整数N,描述骑士团的人数。接下来N行,每行两个正整数,按顺序描述每一名骑士的战斗力
和他最痛恨的骑士。
应包含一行,包含一个整数,表示你所选出的骑士军团的战斗力。
N ≤ 1 000 000,每名骑士的战斗力都是不大于 1 000 000的正整数。
Solution
好久没写博客了,就来写个这道树形DP的题解吧。
题目大意是选出战斗力总和最高的骑士
团,但是每个骑士都有一个他认为长得丑的骑士,于是他们就不能在一起了~~
好了不扯了。首先骑士a认为骑士b长得丑,那么选了b就不能选a,同样选了a也不能选b了,也就是说a、b只能选一个,那么就要连无向边而不是有向边了。
如果没有环的话,这道题就是一个简单的树形DP,问题是有环。稍加分析就会发现,树上有且只有一个环(这好像是是叫环套树或基环树)。对于有环的问题,一般是断环为链,随便找条环上的边删去即可。那么我们就要考虑删去边后如何保留这条边原来的影响,其实一条边就表示一个限制条件,假设删去边(u,v),可以以u为根但不选u跑一遍DP,再以v为根但不选v跑一遍DP,取两者的的最大值就好啦~ 有人可能会问为什么可以随便找一条环上的边删去,因为我们其实只是把一条边转换为了直接的条件,让有环图转换为了无环图,所以原图的所有性质其实是不变的。
还有一个问题,原题没有保证图联通,所以这其实是一个基环树森林(真du liu),所以要把每棵树上的最大值加起来才是最终答案。
DP的话用f[x][0]和f[x][1]分别表示x选与不选,很容易得到状态转移方程:
f[x][0]+=max(f[y][0],f[y][1]);(x不选,则y可选可不选,取两者最大值)
f[x][1]+=f[y][0];(x选,则y一定不可选)
其中y∈x的儿子集。
找环的话笔者用的是并查集。好了,去吧代码菌:
#include<cstdio>
#include<cstring>
#include<cctype>
#include<utility>
#include<algorithm>
using namespace std;
const int N=1e6+5;
int n,m,sco[N],father[N];
long long ans,t,f[N][2];
pair<int,int> cut[N];
struct Graph{
struct edge{
int v,last;
}e[N<<1];
int tot,tail[N];
inline void add(int x,int y){
e[++tot]=(edge){y,tail[x]};
tail[x]=tot;
}
}G;
inline int find(int x){return father[x]==x?x:father[x]=find(father[x]);}
inline int read()
{
int X=0,w=0; char ch=0;
while(!isdigit(ch)) {w|=ch==‘-‘;ch=getchar();}
while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
return w?-X:X;
}
void DP(int x,int fa){
register int p,y;
f[x][1]=(long long)sco[x],f[x][0]=0;
for(p=G.tail[x];p;p=G.e[p].last){
y=G.e[p].v;
if(y==fa) continue; //防止走重边
DP(y,x);
f[x][0]+=max(f[y][0],f[y][1]);
f[x][1]+=f[y][0];
}
}
int main()
{
register int i,y;
n=read();
for(i=1;i<=n;++i) father[i]=i;
for(i=1;i<=n;++i){
sco[i]=read(),y=read();
if(find(i)!=find(y))
G.add(i,y),G.add(y,i),father[father[y]]=father[i];
else cut[++m].first=i,cut[m].second=y; //cut存要删去的边的起点和终点
}
for(i=1;i<=m;++i){
DP(cut[i].first,0);
t=f[cut[i].first][0]; //不选起点,因此第二下标只考虑0,下面同理
DP(cut[i].second,0);
t=max(t,f[cut[i].second][0]);
ans+=t; //把每棵树上的最大值加起来
}
printf("%lld",ans);
return 0;
}
希望能帮到大家,请多多指教.
2018-09-02