BZOJ 3037. 创世纪

传送门

一眼基环树森林上面搞搞 $dp$

本来如果是颗树,直接设 $f[x][0/1]$ 表示节点 $x$ 不选/选 时子树的最大价值

因为有环,所以设 $f[x][0/1/2]$ 表示节点 $x$ 不选/选且有非环上儿子控制/选且没非环上儿子控制 时非环上子树的最大价值

对环上每个节点往子树内跑一遍 $dp$,然后在环上分类讨论一波,断环为链,对于环上第一个节点 $x$,分成 $3$ 种情况

$1.$ 不选,$2.$ 选且非环儿子控制,$3.$ 选且强制环儿子控制,三种情况取个 $max$ 即可

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
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<<1)+(x<<3)+(ch^48); ch=getchar(); }
    return x*f;
}
const int N=2e6+7,INF=1e9+7;
int fir[N],from[N<<1],to[N<<1],cntt;
inline void add(int a,int b) { from[++cntt]=fir[a]; fir[a]=cntt; to[cntt]=b; }
int n,fa[N],f[N][3],g[N][2],ans;
bool ring[N],vis[N];
int st[N],Top;
void dfs(int x)
{
    int sum=0,mi=INF; vis[x]=1;
    for(int i=fir[x];i;i=from[i])
    {
        int &v=to[i]; if(v==fa[x]||ring[v]) continue;
        dfs(v); sum+=max(f[v][0],f[v][1]);
        if(f[v][0]>=f[v][1]) mi=0;
        else mi=min(mi,f[v][1]-f[v][0]);
    }
    f[x][0]=sum; f[x][1]=sum-mi+1; f[x][2]=sum+1;
}
void DP()
{
    for(int i=2;i<=Top;i++)
    {
        g[i][0]=f[st[i]][0]+max(g[i-1][0],g[i-1][1]);
        g[i][1]=max( f[st[i]][1]+max(g[i-1][0],g[i-1][1]) , f[st[i]][2]+g[i-1][0] );
    }
}
int solve(int x)
{
    int res=0,t=x; Top=0;
    while(!vis[t]) vis[t]=1,t=fa[t];
    while(!ring[t]) ring[t]=1,st[++Top]=t,t=fa[t];
    for(int i=1;i<=Top;i++) dfs(st[i]);
    g[1][0]=f[st[1]][0]; g[1][1]=-INF; DP();
    res=max(g[Top][0],g[Top][1]);
    g[1][0]=-INF; g[1][1]=f[st[1]][1]; DP();
    res=max(res,max(g[Top][0],g[Top][1]));
    g[1][0]=-INF; g[1][1]=f[st[1]][2]; DP();
    res=max(res,g[Top][0]);
    return res;
}
int main()
{
    n=read();
    for(int i=1;i<=n;i++) fa[i]=read(),add(fa[i],i);
    for(int i=1;i<=n;i++)
        if(!vis[i]) ans+=solve(i);
    printf("%d\n",ans);
    return 0;
}

 

posted @ 2019-09-10 07:31  LLTYYC  阅读(177)  评论(0编辑  收藏  举报