P1364 医院设置 题解

博客园同步

原题链接

简要题意:

求带权树的重心。

带权树的重心定义:用 d i s x , y dis_{x,y} 表示 x x y y 的距离( x = y x=y d i s x , y = 0 dis_{x,y} = 0 ),即求一个节点 u u ,最小化:

i = 1 n w i × d i s i , u \sum_{i=1}^n w_i \times dis_{i,u}

本题要求求这个最小值。

首先,按照题目要求建树,分算法讨论。

算法一

1 n 100 1 \leq n \leq 100

显然,我们可以从每个点开始搜索,用 O ( n 2 ) O(n^2) 的时间求出 dis \text{dis} 数组。

然后,再枚举重心取最小值即可完成答案。

时间复杂度: O ( n 2 ) O(n^2) .

期望得分: 100 p t s 100pts .

算法二

注意到,如果本题加强为:

1 n 1 0 6 1 \leq n \leq 10^6

那么我们需要一些高效算法。

这里我们考虑 换根 dp \text{dp} .

即我们先算出 u = 1 u = 1 的答案 f u f_u ,然后对于一组父子关系 u , v u , v u u v v 的父亲),那么 f u f_u f v f_v 有什么关系呢?

我们只需要考虑两部分:

  • 原来在 v v 的子树中的数,离 v v u u 1 1 ,所以答案集体 1 -1 .

  • 原来不在 v v 子树中的数,离 v v u u 1 1 ,所以答案集体 + 1 +1 .

综上可得:

f v = f u + ( s i z 1 s i z v ) s i z v f_v = f_u + ( siz_1 - siz_v ) - siz_v

其中 s i z i siz_i i i 的子树权值和。 s i z 1 siz_1 即全树权值和( 1 1 为根), s i z 1 s i z v siz_1 - siz_v 为不在 v v 子树中的 + 1 +1 ,在的 1 -1 s i z v \therefore - siz_v .

有了这个 dp \text{dp} ,我们只需要搜索初始化 s i z siz f 1 f_1 就可以啦!

时间复杂度: O ( n ) O(n) .

实际得分: 100 p t s 100pts .

#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;

const int N=2e5+1;

inline int read(){char ch=getchar();int f=1; while(!isdigit(ch)) {if(ch=='-') f=-f; ch=getchar();}
	   int x=0;while(isdigit(ch)) x=x*10+ch-'0',ch=getchar(); return x*f;}

int n,ans,w[N],siz[N],f[N];
vector<int> G[N];

inline void dfs(int u,int fa,int dep) {
//当前节点为 u , 父亲为 fa , 深度为 dep
	siz[u]=w[u]; //先处理自己
	for(int i=0;i<G[u].size();i++) {
		int v=G[u][i]; if(v==fa) continue;
		dfs(v,u,dep+1); siz[u]+=siz[v]; //统计儿子节点
	} f[1]+=w[u]*dep; //记录 f[1] 的答案
}

inline void dp(int u,int fa) {
	for(int i=0;i<G[u].size();i++) {
		int v=G[u][i]; if(v==fa) continue;
		f[v]=f[u]+siz[1]-(siz[v]<<1); // <<1 等价于 *2
		dp(v,u); //换根 dp
	} ans=min(ans,f[u]); //统计答案
}

int main(){
	n=read(); ans=INT_MAX;
	for(int i=1;i<=n;i++) {
		w[i]=read(); int u=read(),v=read();
		if(v) G[i].push_back(v),G[v].push_back(i);
		if(u) G[i].push_back(u),G[u].push_back(i); //建树
	} dfs(1,0,0); dp(1,0); //初始化,然后换根 dp
//	for(int i=1;i<=n;i++) printf("%d ",siz[i]); puts(""); 
//	for(int i=1;i<=n;i++) printf("%d ",f[i]); puts("");
	printf("%d\n",ans);
	return 0;
}


发布了57 篇原创文章 · 获赞 80 · 访问量 8156

猜你喜欢

转载自blog.csdn.net/bifanwen/article/details/105356537
今日推荐