[oiclass3976]牧羊女:树形DP+换根
题目
天色已暗,女牧羊人需要把她的羊群聚集到一起。
女牧羊人牧羊的地方有 \(n\) 块大草坪,它们由 \(n-1\) 条道路连接,保证草坪之间两两联通。女牧羊人有两种羊:普通的羊和领头羊。最开始,每块草坪上都有且仅有一只领头羊和一只普通羊。女牧羊人每次可以选择两块相邻的草坪 \(x\) 和 \(y\),并进行以下两种操作中的任意一个:
- 将 \(x\) 的一部分普通羊迁移到 \(y\) 中。
- 将 \(x\) 的一部分领头羊和 所有的普通羊 迁移到 \(y\) 中。由于领头羊不善于领导以前并不跟随自己的普通羊,所以 进行该操作前 \(y\) 草坪不能有普通羊。
此外,每种操作也是需要花费时间的:迁移 一只 领头羊一次的时间为 \(A\),迁移 一只 普通羊一次的时间为 \(B\)(一次是指从一块草坪到另一个相邻的草坪)。女牧羊人需要在尽可能少的时间内把所有的羊迁移到同一块草坪上。你能帮帮她吗?
题目链接
题解
首先要理解题意,找出一般做法,我们结合样例进行理解。
样例输入:
6 2 3
1 4
4 2
4 6
2 3
2 5
样例输出:
65
对于该样例,如果我们选用点1为根结点,将所有羊移动到根结点,所需的费用是85,如果我们选用4为根结点,所需的费用是65,我们看看当选4为根结点时费用是如何计算出来的。
首先,我们可以将所有普通羊集中到点1(其实点6也可以)所需费用为\(B*(1+1+3+1+5)\),然后将所有除1之外的领头羊集中到点4,所需费用为\(A*(1+1+3+1)\),最后将1结点的所有羊移动到4结点,所需费用为\(A*1+B*6\)。
仔细分析,我们可以得出一般操作策略,即如果将羊集中到点u,那么我们需要先将所有普通羊集中到点u的儿子v,然后将所有领头羊集中到点u(除v点的领头羊),最后进行一次操作二,将所有v点的羊集中到u点。可以证明,当v为叶子结点时,所需费用最优。
设size[u]表示以u为根的子树中结点的数量,设\(sum[u]=\sum_{v\in u子树内的结点}size[v]\),简单理解就是u子树的结点移动到u的单位费用和。设u为根,v为u的一个叶子儿子,考虑两种情况:
1、移动所有领头羊到点u,其代价为\(A*(sum[u]-size[u])\),减去\(size[u]\)是因为移动到u之后就不用再移动了,因为u为根结点,所以\(size[u]=n\)。
2、移动所有普通羊到点v,再将所有普通羊从点v移动到u,所有普通羊移动到点v的代价为\(B*(sum[u]-size[v]-size[v])\),然后将所有普通羊从点v移动到根u的代价为\(B*n\),当v为叶子结点时,\(size[v]=1\),所以总代价为\(B*(sum[u]-2+n)\)。
综合考虑以上两种情况,将所有羊移动到根u的代价为\(A*(sum[u]-n)+B*(sum[u]-2+n)\),由公式看出,这个代价只和sum[u]有关。
如果我们需要将根u换为跟根v,则我们需要更新sum[v],即以v为根所有点移动到v的代价。容易得到\(sum[v]=sum[u]-size[v]-size[v]+n\)。更新了sum[v],即可以更新以v为根的最小费用。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=2*1e5+5;
int n,a,b,x,y;
long long size[N],sum[N],ans=1e18;
vector<int> g[N];
void dfs(int u,int fa){
size[u]=1;
for(int i=0;i<g[u].size();i++){
int v=g[u][i];
if(v==fa)continue;
dfs(v,u);
size[u]+=size[v];
sum[u]+=sum[v];
}
sum[u]+=size[u];
}
void change_root(int u,int fa){
ans=min(ans,a*(sum[u]-n)+b*(sum[u]+n-2);
for(int i=0;i<g[u].size();i++){
int v=g[u][i];
if(v==fa)continue;
sum[v]=sum[u]-2*size[v]+n;
change_root(v,u);
}
}
int main(){
scanf("%d %d %d",&n,&a,&b);
for(int i=1;i<n;i++){
scanf("%d %d",&x,&y);
g[x].push_back(y);
g[y].push_back(x);
}
dfs(1,-1);
change_root(1,-1);
printf("%lld",ans);
}