一眼基环树森林上面搞搞 $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 ",ans); return 0; }