1. 程式人生 > >[AGC009B Tournament] [建模+貪心]

[AGC009B Tournament] [建模+貪心]

[題目大意]

    n個人參加錦標賽,比賽有若干場,每場都是兩個倖存者比試,淘汰失敗方。現在不清楚具體怎麼比的,只知道1號為最終冠軍,其餘每個人,i號是被a[i]打敗的,讓你在所有可能的比賽流程樹中找出深度最小的那個,輸出深度。

[思路]

    考慮建一個新樹,其中i的父親是a[i],這個時候我們發現,i的子樹中只有i倖存下來,接下來的比賽和i的後代沒有任何關係,即沒有後效性,所以我們顯然可以貪心地記錄一個g[i],表示i的子樹全部比完後,形成的以i為根的競賽樹的深度dep[i]的最小值,接著向上合併。

    討論一個點有多個兒子的情況。觀察發現,這個點x會依次和每個兒子j節點比一場,每一次比賽相當於令dep[x]=max(dep[x],g[j])+1,我們只需要找到一個最優合併順序,使得dep[x]儘量小就可以了。不難證明最優策略是按g[j]從小到大的順序依次合併。

    複雜度O(nlogn)。

#include <cstdio>
#include <algorithm>
#define rep(i,j,k) for (i=j;i<=k;i++)
using namespace std;
const int N=1e5+5;
int n,i,l,r,x,y,f,bn,son[N],fs[N],bro[N],fa[N],q[N],b[N],g[N];
void add(int x,int y) {
	bro[y]=fs[x]; fs[x]=y;
}
int main()
{
	//freopen("tournament.in","r",stdin);
	//freopen("tournament.out","w",stdout);
	scanf("%d",&n);
	rep(i,2,n) scanf("%d",&fa[i]),son[fa[i]]++,add(fa[i],i);
	rep(i,1,n) if (!son[i]) q[++r]=i;
	for (l=1;l<=r;l++)
	{
		x=q[l];
		if (fs[x]) {
			for (y=fs[x],bn=0;y;y=bro[y]) b[++bn]=g[y];
			sort(b+1,b+1+bn);
			rep(i,1,bn) g[x]=max(g[x],b[i])+1;
		}
		if (x==1) break;
		f=fa[x]; son[f]--; if (!son[f]) q[++r]=f;
	}
	printf("%d\n",g[1]);	
	return 0;
}