一个错误结论:尽量让权值最大的先被选走。
而我们可以很容易的构造出一个反例(不赘述了)
但由这个错误的结论,我们可以得到一个正确的结论:对于当前权值最大的点,一定在他父亲被染过色后第一个被染色,相当于是紧接着父亲被染色。
根据这个原则,我们可以把相邻染色的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;
}