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;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值