• [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

    愿你有一天能和重要的人重逢
  • 相关阅读:
    输入一个nxn矩阵各元素的值,球出两条对角线元素之和
    打印杨辉三角
    编写一个函数,实现两个字符串的连接功能
    字符串置换。将字符串s中的出现的字符s1用字符s2置换
    有一行文字,要求删去其中某个字符
    自定义函数delstr()的功能是删去字符串s1中所有的"*"
    用微软的kestrel在Linux上利用Apache架设Asp.Net Core环境
    2012年8月14日 星期二 equals()方法 (冲突备份)
    jquery 操作DOM 案例
    FileUpload 控件上传图片和文件
  • 原文地址:https://www.cnblogs.com/gosick/p/9574587.html
Copyright © 2020-2023  润新知