zoukankan      html  css  js  c++  java
  • P2607 [ZJOI2008]骑士

    基环树DP

    什么叫基环树DP啊?

    谢罪谢罪

    在图论中,树被视作为一种特殊的图G=(V, E),其中|V| = |E|+1。其存在如下特性:

    1.树G上任意两点必定能够通过途经若干边后到达
    2.任意两点间的路径必然唯一,即不存在环
    3.将树G上任意一条边删去,该图即成为非连通图
    4.在G中任意不相连两点间插入一条边,该新图G’ =(V, E’)正好含有一个环
    基环树的概念即是从上述特性4所引申出的特殊的树。虽然其不符合树|V| = |E| + 1的特征,但由于其特殊性——删除环上任意一条边即可成为树,故仍将其视作”树”来解决问题。

    因此可以枚举删边进行树上DP,但要注意保持环的性质,即注意被删边两点之间的关系。

    题目描述

    战火绵延,人民生灵涂炭,组织起一个骑士军团加入战斗刻不容缓!国王交给了你一个艰巨的任务,从所有的骑士中选出一个骑士军团,使得军团内没有矛盾的两人(不存在一个骑士与他最痛恨的人一同被选入骑士军团的情况),并且,使得这支骑士军团最具有战斗力。

    为了描述战斗力,我们将骑士按照1至N编号,给每名骑士一个战斗力的估计,一个军团的战斗力为所有骑士的战斗力总和。

    输入输出格式

    输入格式:

     第一行包含一个正整数N,描述骑士团的人数。

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

    输出格式:

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

    solution

    将每个骑士所憎恨的人和他连一条单向边,再将骑士的父节点设为他憎恨的人,这样就形成了一个图,容易得出这个图中一定存在环,且我们将一个个环断开就会生成一个树,然后就是简单的树上DP(没有上司的舞会),有几点要注意

    1.注意判好所有环,这个图有可能不是联通的,所以每个强连通分量都是独立的

    2.注意断环时要注意两个端点的关系,不能两个同时选,因此可以分别强制一个不选,然后对两个点都进行DP取max

    3.DP过程注意顺便把vis赋值

    4.对于每一个环考虑到树的性质,只对断边的两个端点DP即可。

    code

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    #define ll long long
    #define maxn 2000010
    #define re register
    using namespace std;
    int n,cnt,root,y;
    ll ans,DP[maxn][2];
    int head[maxn],vis[maxn],val[maxn],fa[maxn];
    struct Edge{
        int v,nxt;
    }e[maxn];
    inline void add(int u,int v)
    {
        e[++cnt].v=v;
        e[cnt].nxt=head[u];
        head[u]=cnt;
    }
    inline int read()
    {
        int x=0,f=1; char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
        return x*f;
    }
    void dp(int now)
    {
        vis[now]=1;
        DP[now][0]=0,DP[now][1]=val[now];
        for(int i=head[now];i;i=e[i].nxt)
        {
            int ev=e[i].v;
            if(ev!=root)
            {
                dp(ev);
                DP[now][0]+=max(DP[ev][1],DP[ev][0]);
                DP[now][1]+=DP[ev][0];
            }
            else
            {
                DP[ev][1]=-maxn;//关键,实际上是控制root不选,这样一来枚举两个root就可以了
            }
        }
    }
    void work(int x)
    {
        vis[x]=1;
        root=x;
        while(!vis[fa[root]])
        {
            root=fa[root];
            vis[root]=1;
        }
            dp(root);
            ll tmp=max(DP[root][0],DP[root][1]);
            vis[root]=1;
            root=fa[root];
            dp(root);
            ans+=max(tmp,max(DP[root][0],DP[root][1]));//对两个root分别处理,手推一下更容易理解
        return;
    }
    int main()
    {
        n=read();
        for(re int i=1;i<=n;++i)
        {
            val[i]=read();
            y=read();
            add(y,i);
            fa[i]=y;
        }
        for(re int i=1;i<=n;++i)
        {
            if(!vis[i])
            {
                work(i);
            }
        }
        printf("%lld",ans);
        return 0;
    }
  • 相关阅读:
    java算法--循环队列
    java算法--普通队列
    java算法--稀疏数组
    HelloWorld
    css
    自定义事件并且主动触发
    数组字符串操作
    进阶路上有你我-相互相持篇之ES6里箭头函数里的this指向问题
    关于一道面试题
    异步函数回调
  • 原文地址:https://www.cnblogs.com/Liuz8848/p/10833532.html
Copyright © 2011-2022 走看看