[基环外向树+树形DP]BZOJ 1040—— [ZJOI2008]骑士

来源:互联网 发布:达内java速成班 编辑:程序博客网 时间:2024/06/06 04:14

题目描述

有n个骑士,每个骑士有一个不想一起组队的人。

每个骑士还有一个战斗力,求战斗力最大的队伍。

解题思路

把每个骑士和不想组队的骑士之间连边,会形成一堆基环外向树形成的森林。

假设每个联通块是树,边数=n-1,显然不符合。

假设每个联通块存在两个及以上的简单环,边数>n,也不符合。

所以图必然是基环外向树形成的森林。

先随意建树寻找返祖边,确定两个点和一条边。

分别以这两个点为树根并砍掉这条边进行树形DP,f[i][0/1]分别表示这个节点选或不选的最大战斗力,转移状态显然。

这样做的好处是,DP最后的根节点都不选,累计两次的最大值好了。

#include<cstdio>#include<algorithm>#define LL long longusing namespace std;const int maxn=1000005;int lnk[maxn],son[2*maxn],nxt[2*maxn],tot;int a[maxn],n,hx,hy,hc;LL ans,f[maxn][2];bool vis[maxn];inline int _read(){    int num=0;char ch=getchar();    while(ch<'0'||ch>'9') ch=getchar();    while(ch>='0'&&ch<='9') num=num*10+ch-48,ch=getchar();    return num;}void add(int x,int y){nxt[++tot]=lnk[x];lnk[x]=tot;son[tot]=y;}void DFS(int x,int fa){    vis[x]=1;    for (int j=lnk[x];j;j=nxt[j]) if (!vis[son[j]]) DFS(son[j],x);    else if (son[j]!=fa) hx=x,hy=son[j],hc=(j+1)/2;}void DP(int x,int fa){    f[x][0]=0,f[x][1]=a[x];    for (int j=lnk[x];j;j=nxt[j])    if (son[j]!=fa&&((j+1)/2)!=hc){        DP(son[j],x);        f[x][0]+=max(f[son[j]][1],f[son[j]][0]);        f[x][1]+=f[son[j]][0];    }}LL work(int x){DP(x,0);return f[x][0];}int main(){    freopen("exam.in","r",stdin);    freopen("exam.out","w",stdout);    n=_read();    for (int i=1;i<=n;i++){        a[i]=_read();int x=_read();        add(x,i);add(i,x);    }    for (int i=1;i<=n;i++)if (!vis[i]){        DFS(i,0);        ans+=max(work(hx),work(hy));    }    printf("%lld\n",ans);    return 0;}