一個錯誤結論:儘量讓權值最大的先被選走。
而我們可以很容易的構造出一個反例(不贅述了)
但由這個錯誤的結論,我們可以得到一個正確的結論:對於當前權值最大的點,一定在他父親被染過色後第一個被染色,相當於是緊接着父親被染色。
根據這個原則,我們可以把相鄰染色的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;
}