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