upc51-种树 实现:树形dp+换跟+剪枝

 状态

题目描述

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

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值