BZOJ 1040: [ZJOI2008]骑士(基环树dp)

 http://www.lydsy.com/JudgeOnline/problem.php?id=1040

题意:

 

思路:

这是基环树,因为每个人只会有一个厌恶的人,所以每个节点只会有一个父亲节点,但是根节点也是有父亲节点的,所以在树中肯定是存在一个环的,只要删除该环中的任意一条边,那么就能将该图变成一颗树。

如果是树的话,那就很简单了,d[u][0/1] dp求解即可。

现在假设删除的边是e,两端的节点分别是u,v,首先对u为根的树作一次dp,最后取d[u][0](v取不取都无所谓),不能取d[u][1](因为此时可能也取了v)。但是这样的话没有考虑选u的情况,所以再对v为根的树作一次dp,最后取d[v][0]。两者取大者即可。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<map>
using namespace std;
const int maxn = 1000000+5;
typedef long long ll;

int n,tot=0,edgeID,edgeLeft,edgeRight;
int head[maxn],vis[maxn];
ll val[maxn], d[maxn][2];

struct node
{
    int v,next;
}e[2*maxn];

void addEdge(int u,int v)
{
    e[tot].v = v;
    e[tot].next = head[u];
    head[u] = tot++;
}

void dfs(int u, int fa)
{
    vis[u] = 1;
    for(int i=head[u];i!=-1;i=e[i].next)
    {
        int v = e[i].v;
        if(v == fa)  continue;
        if(!vis[v])  dfs(v,u);
        else   //找到了环
        {
            edgeID = i;  //记录边和两端顶点
            edgeLeft = u;
            edgeRight = v;
        }
    }
}

ll dp(int u, int fa)
{
    d[u][0] = 0, d[u][1] = val[u];
    for(int i=head[u];i!=-1;i=e[i].next)
    {
        int v = e[i].v;
        if(v==fa)  continue;
        if(i==edgeID || i==(edgeID^1))  continue;  //正向边和反向边
        dp(v,u);
        d[u][0] += max(d[v][0],d[v][1]);
        d[u][1] += d[v][0];
    }
    return d[u][0];
}

int main()
{
    //freopen("in.txt","r",stdin);
    memset(head,-1,sizeof(head));
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        int x;
        scanf("%lld%d",&val[i],&x);
        addEdge(i,x);
        addEdge(x,i);
    }
    ll ans = 0;
    for(int i=1;i<=n;i++)
    {
        if(vis[i])  continue;
        dfs(i,-1);
        ans += max(dp(edgeLeft,-1),dp(edgeRight,-1));
    }
    printf("%lld\n",ans);
    return 0;
}

  

 

posted @ 2017-12-16 21:52  Kayden_Cheung  阅读(173)  评论(0编辑  收藏  举报
//目录