poj2054-color a tree(貪心)

一個錯誤結論:儘量讓權值最大的先被選走。

而我們可以很容易的構造出一個反例(不贅述了)

但由這個錯誤的結論,我們可以得到一個正確的結論:對於當前權值最大的點,一定在他父親被染過色後第一個被染色,相當於是緊接着父親被染色。

根據這個原則,我們可以把相鄰染色的2個點都通過並查集合併成一個大的結點,得出他們的相對位置(拓撲序)然後再繼續找下一個結點,這樣每次合併都會少一個結點,最終只有一個結點的時候染色就結束了。

PS:

1.注意連接的時候,是把子節點的最上層練到父節點的最下層。(這裏應通過並查集尋找一下這2個結點)

2.對於合併後的點的權值,應該賦值爲總和的平均值。考慮將兩個大結點不同的合併序列相建可以得到:(a1+a2+a3...+an)*m-(a1+a2+..+am)*n比較與0的大小,同時除以m*n,就得到了2個平均值的差。

3.對於並查集的應用,因爲我們既要知道最頂層也要知道最底層的,所以需要2個並查集數組,這是一個巧妙地改造。

over

#include <iostream>
#include <cstring>
#include <cstdio>

using namespace std;
const int maxn=1005;
int n,root;
int a[maxn];bool vis[maxn];int fa[maxn];
int nxt[maxn],ff[maxn];double b[maxn];int cnt[maxn];
int findch(int x)
{
	if(nxt[x]==x) return x;
	return findch(nxt[x]);	
}
int findfa(int x)
{
	if(ff[x]==x) return x;
	ff[x]=findfa(ff[x]);
	return ff[x];
}
int main()
{
	scanf("%d%d",&n,&root);
	while(n!=0||root!=0)
	{
		memset(vis,0,sizeof(vis));
		for(int i=1;i<=n;i++) 
		{
			scanf("%d",&a[i]);
			b[i]=a[i]*1.0;	
			nxt[i]=i;fa[i]=i;cnt[i]=1;ff[i]=i;
		}
		for(int i=1;i<n;i++) 
		{
			int x,y;scanf("%d%d",&x,&y);
			fa[y]=x;
		}
		double maxv;vis[root]=1;
		for(int j=1;j<n;j++)
		{
			maxv=0;int num;
			for(int i=1;i<=n;i++)
			{
				if(b[i]/cnt[i]>maxv&&!vis[i])
				{
					maxv=b[i]/cnt[i];
					num=i;
				}
			}
			int u=findch(fa[num]);
			nxt[u]=num;
			ff[num]=u;
			u=findfa(num);
			cnt[u]=cnt[num]+cnt[u];
			b[u]=b[num]+b[u];
			vis[num]=1;
		}
		int ans=0,k=1;
		while(root!=nxt[root])
		{
			ans+=k*a[root];
			//cout<<ans<<endl;
			k++;
			root=nxt[root];
		}
		ans+=k*a[root];
		printf("%d\n",ans);
		scanf("%d%d",&n,&root);
	}
	return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章