[bzoj3037/2068]創世紀[Poi2004]SZP_樹形dp_並查集_基環樹

創世紀 SZP bzoj-3037/2068 Poi-2004ios

題目大意:給你n個物品,每一個物品能夠且僅能夠控制一個物品。問:選取一些物品,使得對於任意的一個被選取的物品來說,都存在一個沒有被選取的物品,並且選取的個數最大。
spa

註釋:$1\le n \le 10^6$。code

想法:顯然,和騎士相似的,是一個基環樹森林。若是A物品能夠控制B物品,那就有B物品向A物品連邊。對於每個基環樹,若是這個基環樹是樹的話顯然變成樹形dp入門題,暴力樹形dp便可。而後對於基環樹來說,咱們依然記錄環上兩點,分別以這兩點爲根,而後特判樹形dp便可。blog

最後,附上醜陋的代碼... ...string

#include <cstdio>
#include <cstring>
#include <iostream>
#define N 1000010 
using namespace std;
int n,m,ans,now,tot;
int to[N],nxt[N],head[N],f[N],g[N],fa[N],ra[N],rb[N];
inline void add(int x,int y)
{
	to[++tot]=y;
	nxt[tot]=head[x];
	head[x]=tot;
}
int find(int x)
{
	return (fa[x]==x)?x:(fa[x]=find(fa[x]));
}
void dfs(int x)
{
	int t=1<<30;
	g[x]=0;
	for(int i=head[x];i;i=nxt[i])
	{
		if(to[i]!=now)
			dfs(to[i]);
		g[x]+=max(f[to[i]],g[to[i]]);
		t=min(t,max(f[to[i]],g[to[i]])-g[to[i]]);
	}
	f[x]=g[x]+1-t;
}
int main()
{
	scanf("%d",&n);
	int a;
	for(int i=1;i<=n;i++)
		fa[i]=i;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a);
		if(find(a)!=find(i))
		{
			add(a,i);
			fa[fa[a]]=fa[i];
		}
		else
			ra[++m]=a,rb[m]=i;
	}
	for(int i=1;i<=m;i++)
	{
		dfs(ra[i]),now=ra[i];
		dfs(rb[i]),a=f[rb[i]];
		f[ra[i]]=g[ra[i]]+1;
		dfs(rb[i]),ans+=max(a,g[rb[i]]);
	}
	printf("%d",ans);
	return 0;
}

 小結:基環樹dp是一種常見的,樹形dp帶基環樹的處理方法。這裏有一個問題(By JhinLzh),問什麼輸出答案上面的for循環中的第一個dfs有用?明明在第二個dfs中全部的f和g都被更新了,爲何還要dfs?由於在第一個dfs中咱們對f是強行負值,這樣對於一些葉子節點來說t值是沒有更改的,這就致使f值在最後是一個極小值,這樣的f是不會更新答案的。若是不寫第一個dfs,使得一些在本不能更新答案的點更新了答案,致使答案錯誤。因此第一個dfs是必要的。io

相關文章
相關標籤/搜索