1. 程式人生 > >【BZOJ 3037】 創世紀 樹形DP

【BZOJ 3037】 創世紀 樹形DP

樹的最小支配集:從v中取儘量少的點組成一個集合,使得對於v中剩餘的點都與取出來的點有邊相連。
怎麼求?
如果是正常的樹(無向)的話對於每個節點就是三種狀態:
0.這個點被選
1.這個點被父親節點覆蓋
2.這個點被兒子節點覆蓋
的最小支配集。
對於這道題呢,建反圖後變成外向基環樹林,所以對於狀態就少了一維(沒有狀態2),還要在環上隨便找一個點將環拆開,然後在列舉這個點選不選,若這個點為x,指向的點為y,則:
1.選x,則f[y][1]=0;
2.不選x,正常做最小支配集即可。
最後就是要注意有向圖的找環阿 dfs阿和無向圖的區別阿!!!


#include<cstdio>
#include<cstring> #include<iostream> #include<algorithm> #include<vector> using namespace std; const int inf=0x3f3f3f3f; const int N=1000011; int fa[N],g[N],f[N],in[N]; int head[N],next[N*2],key[N*2],tot,vis[N]; int n,p; void add(int x,int y) { tot++; next[tot]=head[x]; head[x]=tot; key[tot]=y; } void
DFS(int x) { vis[x]=1; if(vis[fa[x]]) p=x; else DFS(fa[x]); } void dfs(int x,int fg,int root) { if(x==fg)g[x]=0,f[x]=1; else g[x]=inf,f[x]=1; vis[x]=1; for(int i=head[x];i;i=next[i]) { int y=key[i]; if(y!=root) { dfs(y,fg,root); g[x]+=min(g[y],f[y]); g[x]=min(g[x],f[x]+f[y]-1
);//列舉有哪一個兒子選了 或哪些兒子選了 f[x]+=min(f[y],g[y]); } } } int main() { cin>>n; for(int i=1;i<=n;i++) { scanf("%d",&fa[i]);//建反向圖 add(fa[i],i); } int ans=0; for(int i=1;i<=n;i++) if(vis[i]==0) { DFS(i);int x=p; int y=fa[x]; if(x) { int tmp=inf; dfs(x,y,x);//強行選x這個點 tmp=min(tmp,f[x]); dfs(x,-1,x);//不選x tmp=min(tmp,g[x]); ans+=tmp; } } printf("%d",n-ans); }