[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

image

对于该样例,如果我们选用点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);
}
posted @ 2022-01-09 12:19  chxulong  阅读(282)  评论(0编辑  收藏  举报