[Bzoj1040][ZJOI2008]骑士

Description

  Z国的骑士团是一个很有势力的组织,帮会中汇聚了来自各地的精英。他们劫富济贫,惩恶扬善,受到社会各界的赞扬。最近发生了一件可怕的事情,邪恶的Y国发动了一场针对Z国的侵略战争。战火绵延五百里,在和平环境中安逸了数百年的Z国又怎能抵挡的住Y国的军队。于是人们把所有的希望都寄托在了骑士团的身上,就像期待有一个真龙天子的降生,带领正义打败邪恶。骑士团是肯定具有打败邪恶势力的能力的,但是骑士们互相之间往往有一些矛盾。每个骑士都有且仅有一个自己最厌恶的骑士(当然不是他自己),他是绝对不会与自己最厌恶的人一同出征的。战火绵延,人民生灵涂炭,组织起一个骑士军团加入战斗刻不容缓!国王交给了你一个艰巨的任务,从所有的骑士中选出一个骑士军团,使得军团内没有矛盾的两人(不存在一个骑士与他最痛恨的人一同被选入骑士军团的情况),并且,使得这支骑士军团最具有战斗力。为了描述战斗力,我们将骑士按照1至N编号,给每名骑士一个战斗力的估计,一个军团的战斗力为所有骑士的战斗力总和。

Input

  第一行包含一个正整数N,描述骑士团的人数。接下来N行,每行两个正整数,按顺序描述每一名骑士的战斗力
和他最痛恨的骑士。

Output

  应包含一行,包含一个整数,表示你所选出的骑士军团的战斗力。

Sample Input

3
10 2
20 3
30 1

Sample Output

30

HINT

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

愿你有一天能和重要的人重逢
原文地址:https://www.cnblogs.com/gosick/p/9574587.html