P2726 [SHOI2005]树的双中心 题解

CSDN同步

原题链接

简要题意:

给定一棵树,\(d_{x,y}\) 为 \(x\) 与 \(y\) 距离(\(d_{x,x} = 0\)),选出两个点 \(x,y\),最小化:

\[\sum_{u \in V} (w_u \times \min(dis_{x,u} , dis_{y,u}))
\]

这种水的树形dp 黑题,没几个人做真是太可惜了

首先我们要明白这个式子是什么意思。

\(\min (dis_{x,u} , dis_{y,u})\),就是在 \(x\) 和 \(y\) 中找到较近的那一个的距离。

\(w_u\) 就是点权,\(\sum_{u \in V}\) 是枚举所有节点。

即,所有节点的点权 \(\times\) 离 \(u,v\) 较小的距离之和。

那么,如果只要求一个 \(u\),就是 树的重心,也就是本题的弱化版:

P1364 医院设置

那么,现在变成了 双重心(其实重心比中心形象一点),怎么做?

算法一

考虑一个 \(O(n^2)\) 的做法。

显然,对于任意一组 \(x,y\),会有一个 点集 它们都离 \(x\) 较近,另一个 点集 离 \(y\) 较近,这两个点集的分界是一条边。

那么,我们只需要枚举断边(即将树一分为二),形成点集,对两边的点集分别用重心模板求出,将答案之和取最小值。

枚举断边的时间:\(O(n)\).

取重心,算答案的时间:\(O(n)\)

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

期望得分:\(0\) ~ \(100pts\).(出题人没给部分分,洛谷评测机跑得快)

算法二

显然,枚举断边无法优化,那我们考虑优化取重心。

下面我们要引出一些 树链剖分 的知识。

一个节点 \(i\),它所有儿子 \(u \in son_v\) 中,\(siz _ u\)(子树权值和) 最大那个,我们称之为 重儿子,其余是 轻儿子若干重儿子形成链是重链。

那么,以 \(i\) 为根的子树的重心,如果不在 \(i\),那么,重心是在重儿子的子树中,还是轻儿子的子树中?

常识告诉我们,肯定是在重儿子的子树中比较好啊。(读者可自证)

所以,我们只需要初始化 每个节点的重儿子 编号即可。

但是有个问题:万一我断边,正好把重儿子的边断掉了呢?

所以,我们还要处理 每个节点的次重儿子,重儿子没了的时候用次重儿子。

然后,我们只需要枚举 重链 上的点作为重心的答案即可。

时间复杂度:\(O(n \times h)\)(\(h\) 为树高,因为重链长度 \(\leq h\),这也是就是题目明确说明 “树高 \(\leq 100\)” 的用意所在啊)

实际得分:\(100pts\).

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

const int N=5e4+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 val[N],cdson[N],zdson[N];
int ans=INT_MAX,siz[N],f[N];
int w[N],n,dep[N],cut;
vector<int> G[N];

inline void dfs(int u,int fa) {
//当前节点为 u , 父亲节点为 fa
	siz[u]=w[u]; f[u]=fa; dep[u]=dep[fa]+1; //初始化子树权值和 , 父亲节点 , 深度
	for(int i=0;i<G[u].size();i++) {
		int v=G[u][i]; if(v==fa) continue;
		dfs(v,u); siz[u]+=siz[v];
		val[u]+=val[v]+siz[v]; //换根 dp 的工具
		if(siz[v]>siz[zdson[u]]) cdson[u]=zdson[u],zdson[u]=v;
		else if(siz[v]>siz[cdson[u]]) cdson[u]=v; //求重儿子和次重儿子
	}
}

inline void getans(int u,int now,int all,int &res) {
	res=min(res,now); int v=zdson[u];
	if(v==cut || siz[cdson[u]]>siz[zdson[u]]) v=cdson[u];
	if(!v) return;
	if((siz[v]<<1)>all) getans(v,now+all-(siz[v]<<1),all,res);
} //得到以当前节点为重心的答案

inline void solve(int u) {
	for(int i=0;i<G[u].size();i++) {
		int v=G[u][i]; if(v==f[u]) continue;
		cut=v; int A=INT_MAX,B=INT_MAX;
		for(int now=u;now;now=f[now]) siz[now]-=siz[v]; //断边 , 所有祖先子树大小减少
		getans(1,val[1]-val[v]-dep[v]*siz[v],siz[1],A);
		getans(v,val[v],siz[v],B); ans=min(ans,A+B); //得到两边重心答案 , 统计
		for(int now=u;now;now=f[now]) siz[now]+=siz[v]; //加回来
		solve(v); //继续走
	}
}

int main(){
	n=read(); dep[0]=-1;
	for(int i=1;i<n;i++) {
		int u=read(),v=read();
		G[u].push_back(v);
		G[v].push_back(u); //建树
	} for(int i=1;i<=n;i++) w[i]=read();
	dfs(1,0); solve(1);
	printf("%d\n",ans);
	return 0;
}

原文地址:https://www.cnblogs.com/bifanwen/p/12651344.html

时间: 2024-10-11 03:47:39

P2726 [SHOI2005]树的双中心 题解的相关文章

【BZOJ3302】[Shoi2005]树的双中心 DFS

[BZOJ3302][Shoi2005]树的双中心 Description Input 第一行为N,1<N<=50000,表示树的节点数目,树的节点从1到N编号.接下来N-1行,每行两个整数U,V,表示U与V之间有一条边.再接下N行,每行一个正整数,其中第i行的正整数表示编号为i的节点权值为W(I),树的深度<=100 Output 将最小的S(x,y)输出,结果保证不超过19^9 Sample Input 5 1 2 1 3 3 4 3 5 5 7 6 5 4 Sample Outpu

BZOJ3302: [Shoi2005]树的双中心

n<=50000的树,深度<=100,有点权,选两个点x,y,使最小. dis取了min之后,整个树就会以某条边为分界线分成两半,一半归一个点管.如果是两棵完全独立的树的话,那肯定分别取这两棵树的带权重心.但割掉某条边再找两边重心,这种情况不一定是合法情况.例如: 上图中,虚线边被断开,两边的重心分别是星标节点.这不是一个合法方案,但它显然不如一个合法方案的答案优: 所以放心大胆地割就好了.注意到本题中树的深度h很小,所以割边后涉及的子树信息修改操作都可以暴力修改. 把树以某点为根,希望能预处

DTU/RTU连接双中心配置说明

TCP模式双中心数据传输 实例说明 此实例是使用TCP模式将串口收到的数据转发到服务器上,将服务器发送的数据转发到串口中.在传输过程中不对数据进行加密. 配置服务器网络 1, 确认本机服务器的外网IP地址或域名 A,此实例以外网为固定IP专线   IP地址为220.160.156.233. B,如果外网为拨号上号的方式,就需要使用域名. 条件一: 通过花生壳或是其他域名解析终端绑定外网IP 条件二: 如果只是短时间的测试通信,可以直接使用当前外网IP 查询当前外网IP方法:用百度搜索“IP” 如

浅谈同城双中心的网络部署模型

企业建设数据中心时,出于灾备的考虑,会建设两个甚至多个数据中心.例如我们经常提到的"两地三中心",即同城双中心+异地中心. 同城双中心是指在同城或邻近城市建立两个可独立承担业务的数据中心,双中心具备基本相同的业务处理能力并通过高速链路实时同步数据,日常情况下可同时分担业务及管理系统的运行,并可切换运行:灾难情况下备应急切换,保证业务的持续性.异地灾备中心是指在异地的城市建立一个备份的灾备中心,用于双中心的数据备份,当双中心出现自然灾害等原因而发生故障时,异地灾备中心可以用备份数据进行业

洛谷 P3374 【模板】树状数组 1 题解

此文为博主原创题解,转载时请通知博主,并把原文链接放在正文醒目位置. 题目链接:https://www.luogu.org/problem/show?pid=3374 题目描述 如题,已知一个数列,你需要进行下面两种操作: 1.将某一个数加上x 2.求出某区间每一个数的和 输入输出格式 输入格式: 第一行包含两个整数N.M,分别表示该数列数字的个数和操作的总个数. 第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值. 接下来M行每行包含3或4个整数,表示一个操作,具体如下: 操

树链剖分教程 &amp; bzoj 1036 [ZJOI2008] 树的统计 Count 题解

转载请注明:http://blog.csdn.net/jiangshibiao/article/details/24669751 [原题] 1036: [ZJOI2008]树的统计Count Time Limit: 10 Sec  Memory Limit: 162 MB Submit: 4465  Solved: 1858 [Submit][Status] Description 一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w.我们将以下面的形式来要求你对这棵树完成一些操作: I

Leetcode 与树(TreeNode )相关的题解测试工具函数总结

LeetCode收录了许多互联网公司的算法题目,被称为刷题神器.最近在剑指Offer上也刷了一些题目,发现涉及到数据结构类的题目,比如说"树"."链表"这种题目,如果想在本地IDE进行测试,除了完成题目要求的算法外,还需要写一些辅助函数,比如树的创建,遍历等,由于这些函数平时用到的地方比较多,并且也能加深对常用数据结构的理解,这里将Leetcode中与树(TreeNode)相关题目会用到的测试辅助函数做一个总结. 代码文件说明 LeetCode 剑指Offer在线编

BZOJ1036[ZJOI2008]树的统计Count 题解

题目大意: 一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w.有一些操作:1.把结点u的权值改为t:2.询问从点u到点v的路径上的节点的最大权值 3.询问从点u到点v的路径上的节点的权值和. 思路: 进行轻重树链剖分,再根据每个节点的dfs序建立线段树,维护其最大值以及和,询问时用树剖后的结果将重链作为区间一段一段求和. 代码: 1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #de

【日常学习】【拓扑排序】家谱树&amp;FZU1483 Sicily1424 奖金 题解

拓扑排序的定义 简单来说就是给你一个图写出一个序列 图中如果a通向b 那么序列中A必须排在B前面 拓扑排序可能有很多结果 必须是有向无环图 可以利用拓扑排序来判定环的存在 当然也可以用神奇的SPFA 但是拓扑排序时间复杂度很低 只有O(V+E) 基本实现思路是 每次取出入度为0的点 然后删除与它相连的边 直到没有边  如果还有边但是找不到入度为0的点 说明有环 学习这个算法联系了两道题目 很经典很单纯 但是一般没有OJ有评测 奖金这道题目在福州大学OJ和中山大学萌萌哒Sicily上找到了评测(为