Problem I. World Tree (第14届吉林省赛)

Problem I. World Tree (第14届吉林省赛)

补完题真的收获很多,经典属性dp,有一说一,不看题解完全没有往那方面想,知识点还是不够完备。

题意: 给你一棵树,树上每个点有两个权值a[i]和b[i],每经过一个节点u可以获得a[u]*Σb[v]的贡献,其中v是还未访问过的节点。问以哪种方式遍历整棵树,获得的最大贡献。

在这里插入图片描述
做法:
首先dfs出每个节点作u为根节点的字数中a[ ]和b[ ] 的值,记为suma[u] 和sumb[u]

假设我们当前访问节点u,此时子树中的每个节点肯定还未访问,此时u节点和以u为根节点的子树肯定会产生贡献 a[u] *(sumb[u]-b[u]),这边很好理解,sumb[u]-b[u],就是以u为根节点的子树的b数组之和 * a[u]就是以u为根节点的子树的贡献

还有一部分的贡献就是,和当前访问节点u为兄弟节点的u’ 的子树的贡献,这也是这题最妙的地方。因为我们要确定一个dfs序,也就是访问的先后顺序,使得每一步得到的贡献最大。假设当前节点u和它的兄弟节点u’ ,这时候有两种情况

  • 先访问u节点,再访问u’ 节点,这时候u子树产生的贡献为,sumb[u’] * suma[u],因为访问了整棵以u节点为孩子的子树才会开始访问u’ 子树
  • 同理,先访问u’ 节点再访问u节点,这时候产生的贡献为sumb[u]*suma[u’ ];

所以我们这时候只需要排下序就可以求出先访问哪个节点更优

代码思路就很清楚了:

#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll,ll> P;
const int maxn=1e6+5;
const ll mod=998244353;
//pair<int,int> res[maxn];
//multiset<int> s;
ll dis[maxn];

ll a[maxn],b[maxn],suma[maxn],sumb[maxn],ans;
vector<int> G[maxn];

void dfs(int now ,int pre){
    suma[now] = a[now];
    sumb[now] = b[now];
    for(int i=0;i<G[now].size();i++){
        int x = G[now][i];
        if(x==pre) continue;
        dfs(x,now);
        suma[now] += suma[x];
        sumb[now] += sumb[x];
    }
}

bool cmp(int l,int r){
    return suma[l]*sumb[r]>sumb[l]*suma[r];
}

void dfs1(int u,int pre){
    ans += 1ll*a[u]*(sumb[u]-b[u]);
    sort(G[u].begin(),G[u].end(),cmp);
    ll d = sumb[u] - b[u];   //所有子节点的b的sum,这时候子树中的所有节点都还没被访问
    for(int i=0;i<G[u].size();i++){
        int v = G[u][i];
        if(v==pre) continue;
        dfs1(v,u);  //因为排过序了,遍历当前的v更优
        d-=sumb[v];  //这个v和它的子树访问过了,更新未访问的兄弟节点的sumb
        ans += d*suma[v];
    }
}

void solve(){
    int n;
    cin>>n;
    for(int i=1;i<=n;i++) cin>>b[i],G[i].clear();
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<n;i++){
        int u,v;
        cin>>u>>v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    ans = 0;
    dfs(1,-1);
    dfs1(1,-1);
    cout<<ans<<'\n';
}
int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    ll T=1;
    cin>>T;
    while(T--){
        solve();
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值