【BZOJ 3037】 創世紀 樹形DP
阿新 • • 發佈:2019-02-08
樹的最小支配集:從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);
}