题目描述
BLUESKY007喜欢种树。一天,她得到了一棵n个点的树,其中节点i重量为wi。
在种树之前,BLUESKY007需要用起重机把树吊起。由于她只有一台起重机,所以她只能选择一个点作为受力点。根据BLUESKY007所在世界的物理知识,吊起一棵树需要做的功为
,其中disi表示节点i与受力点之间的距离(边数)。
由于吊起这棵树的费用与所做的功正相关,所以BLUESKY007希望所做的功尽可能小。请你帮助她求出吊起这棵树所做的功的最小值。
输入
第一行包含一个整数n,表示树的点数。
第二行包含n个整数w1,w2,…,wn,其中wi表示节点i的重量。
接下来的n−1行中,每行包含两个数u,v,表示u和v两点之间有连边。
输出
一个整数,表示最小做功。
样例输入 Copy
【样例1】 4 1 2 3 4 1 2 2 3 3 4 【样例2】 4 3 2 1 4 1 2 2 3 3 4
样例输出 Copy
【样例1】 8 【样例2】 12
提示
对于45%的数据,n≤1000。
对于100%的数据,2≤n≤2⋅105,1≤wi≤108,1≤u,v≤n,u≠v。
思路:以任意一个为跟,求出其功,再换跟比较得出最小值
实现:此题构造无向图,以跟为起点,外向扩展遍历
dep[u]为u离跟距离,size[u]为以u为跟子树重量和,w[u]为u重量
剪枝操作:(参考大神)设以1为根结点,dep[u]表示u到根的路径长度,size[u]表示以u为根的子树中所有结点的重量和,当以1为根结点时,dfs一遍求出总做功的值,即sum=∑w[u]∗dep[u]sum=∑w[u]∗dep[u]。接着考虑换根带来的影响,当根从u换到儿子结点v时,v所在的子树少做功size[v],v外的结点多做功size[1]-size[v],即sum′=sum−size[v]+(size[1]−size[v])=sum−2∗size[v]+size[1]sum′=sum−size[v]+(size[1]−size[v])=sum−2∗size[v]+size[1]。
要使sum′≤sumsum′≤sum,则sum−2∗size[v]+size[1]≤sumsum−2∗size[v]+size[1]≤sum,即size[1]≤2∗size[v]size[1]≤2∗size[v],所以在换根时,只有满足2∗size[v]≥size[1]2∗size[v]≥size[1]的儿子结点v才可能使得总做功值变小,可以利用这个条件进行剪枝。
accode:
#include<iostream>
#include <cstring>
using namespace std;
const int N =1e6;
int e[N],ne[N],h[N],idx;
long long size[N],dep[N],sum,ans=1e18;;
int n,w[N];
void dfs(int u,int fa)//fa为u的father
{
sum+=w[u]*dep[u];//以u为跟的总功
size[u]=w[u];//先加本身根节点,以u为跟总重量
for(int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
if(j==fa)continue;//因为无向
dep[j]=dep[u]+1;
dfs(j,u);
size[u]+=size[j];//累加
}
}
void change_root(int u,int fa,long long sum)
{
ans=min(ans,sum);
for(int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
if(j==fa)continue;
long long temp=sum-2*size[j]+size[1];
if(2*size[j]>=size[1])change_root(j,u,temp);
}
}
void add(int a,int b)
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int main()
{
memset(h,-1,sizeof h);
cin>>n;
for(int i=1;i<=n;i++)cin>>w[i];
for(int i=1;i<n;i++){
int a,b;
cin>>a>>b;
add(a,b),add(b,a);
}
dfs(1,-1);
change_root(1,-1,sum);
cout<<ans<<endl;
return 0;
}