思路yxc大佬已经讲得很清楚啦,这里是一点小优化,利用并查集和大根堆(STL priority_queue)将时间复杂度从$O(n^2)$降至$O(nlogn)$
一些要注意的细节:
1. 节点是有可能重复入队的,又因节点的“合并权值”实时更新,所以当它被取出来的时候只需要判重且用它最新的权值即可
2. 记得重置fa数组和vis数组,这是一道多组输入的题目(UVA原题是这样的)
3. 祖先节点是不能入队的,一定要加以判断(因为它不能合并了)
#include <cstdio>
#include <queue>
#include <map>
using namespace std;
const int maxn=1000+10;
struct node
{
int f,s,v;//father,size,value
}a[maxn];
int n,R;
int fa[maxn];
bool vis[maxn];
priority_queue<pair<double, int> >q;
int get(int x) {return fa[x]=fa[x]==x?x:get(fa[x]);}//表示合并之后的祖先节点,可以代表合并之后的集合
int main()
{
while(scanf("%d%d",&n,&R)&&n&&R)
{
int ans=0;
for (int i=1;i<=n;i++)
{
scanf("%d",&a[i].v);
a[i].s=1;
ans+=a[i].v;
if (i!=R)
q.push(make_pair(a[i].v, i));
}
for (int i=1;i<=n-1;i++)
{
int u,v;
scanf("%d%d",&u,&v);
a[v].f=u;
}
for (int i=1;i<=n;i++)
fa[i]=i,vis[i]=0;
while(!q.empty())
{
int p=q.top().second;
q.pop();
if (vis[p])//有可能会重复入队,所以要加以判断
continue;
vis[p]=1;
int f=get(a[p].f);
ans+=a[f].s*a[p].v;
a[f].v+=a[p].v;
a[f].s+=a[p].s;
if (f!=R)
q.push(make_pair((double)a[f].v/a[f].s, f));//新合并的点入队
fa[p]=f;//更新祖先节点
}
printf("%d\n",ans);
}
return 0;
}
感觉有点小疑惑,由于节点的“合并权值”实时更新, 那假如有一个被更新了很多次的节点出队的时候,他的a[f].v/a[f].s 一定是他最新被更新过的那个值吗? 意思是每次合并权值进行更新后 a[f].v/a[f].s 一定会增大吗?
挺久没上来看了,没有及时看到你的消息。因为时隔有点久不太记得清细节了,这里的标记主要是为了防止用相同的信息重复更新的(因为可能会有几个节点和它合并),不一定会增大,但一定是实时的、最新的。我没有细看,如果有疑问就再说吧
不好意思,一定是增大的,因为入队说明被合并了
$$\%\%\% \text{膜拜大佬}$$
……你是在试LaTeX吗
$$\%\%\% \text{膜拜大佬}$$
$$\%\%\%$ \text{膜拜大佬}$$
$$\%\%\%$ \text{膜拜大佬}$
$$\%\%\%$ \text{膜拜大佬}$
$$%%%\text{膜拜大佬}$$