【树的重心】2019CCPC-江西-A-Cotree

上链接:Problem - 6567(HDU归!好耶)

题意

给定两棵不连通的树,分别在其上选定两个节点并连接,使得\sum_{i=1}^n\sum_{j=i+1}^n dis(i,j) 的值最小,其中dis(i,j)代表从节点i到节点j之间的距离。(2<=n<=100000)

分析

举个例子,给出以下一组数据

8
1 2
1 3
3 4
3 5
6 7
6 8
3 6      如图,很明显,需要连接节点3与节点6,能够使得两两节点之间的距离和最小。

顺便提一提,为什么输入数据是n个节点与n-2条边呢?因为要两棵独立树,根据树的性质,n节点的树有n-1条树支,那么两棵独立树就对应有n-2条树支。对于本题中,很显然需要找到两棵树的中心,再将其相连,求出新树的每两点的间距和,即是所求答案。

那么何为树的重心呢?

定义

对于权值为1的树,树的重心即为到其余节点的距离和最短的节点

如何找重心

稍作思考,重心到其余节点的距离和最短,那么可以证明与重心相连的所有子树的最大节点数最小。可以思考这个过程,从重心走向其相邻节点,定会使与新节点相邻的某一子树T的节点数增加,同时相比原节点,新节点离子树T的每一个节点的距离都增加,从而导致到所有节点的距离和增加。

为找到一个节点,使得与其相连的子树最大节点数最小,可以先预处理出每个节点的所有子树节点数之和(包括其本身)的值,存于sum数组中。再遍历所有的节点,依次计算出与当前节点相连的子树最大节点数。对于树型结构,其子儿子(深度比当前节点要深的子树)所包含的节点数即为之前预处理的值,至于另一侧,即与深搜方向相反的那一棵子树T的节点数,可以通过总节点数减去当前节点(包括本身)的儿子数sum得到。遍历所有子儿子(包括子树T),在这个过程中记录使得最大节点数最小的节点,即为所求树的重心。

如图例

 

 知道相关概念之后,接着对本题数据进行操作。

解题过程

存树

考虑到存入的是两棵树,直接按照正常的存图方法即可,本次采用邻接表存图,但为了找出处于不同的两棵树上的不同节点(为了随后更方便地找到树的重心),可采用并查集,将属于同一棵树的节点放在同一个集合,这样就可以很方便的找到原树根(遍历节点,满足s[i]==i的定位初始根)。

预处理儿子数

dfs即可实现,在处理返回值时顺带记录于sum数组中,最终的返回值也可作为节点数,代码如下

ll dfs(ll x,ll pre) {
	ll ans=1;
	for(ll i=0;i<G[x].size();i++) {
		if(G[x][i]==pre) continue;
		ans+=dfs(G[x][i],x);
	}
	return sum[x]=ans;
}

寻找重心

定义函数,引用方式传入重心记录变量,注意相邻最大子树节点数的记录,dfs遍历每一个节点

 代码如下

void dfs_1(ll x,ll ss,ll &newN,ll pre) {
	ll x1=ss-sum[x]; //子树T的节点数 
	for(ll i=0;i<G[x].size();i++) { //遍历儿子 
		if(G[x][i]==pre) continue;
		x1=max(x1,sum[G[x][i]]); //找最大节点数 
	}
	if(x1<maxn) { //寻所有节点中最大节点数最小的节点 
		maxn=x1;
		newN=x;
	}
	for(ll i=0;i<G[x].size();i++) {
		if(G[x][i]==pre) continue;
		dfs_1(G[x][i],ss,newN,x);    //递归深搜 
	}
}

连接树

将两棵树所求得的重心相连,再跑一次查询sum的dfs,计算新树的sum数组值。

跑图出结果

现在离成功仅剩下求出题目所求表达式的值,即所有节点到其余节点的距离和。可通过sum数组计算每条树支被走过的次数,再将这个次数累加。具体实现方法即:对于每一个节点N遍历其相邻子节点Nx,对于当前节点N到子节点Nx的这条连通树支L1,会在从Nx及其子节点出发经过N并到达N的其它子节点时被走过(n-sum[Nx])*sum[Nx]次,(理解成sum[Nx]个节点去走剩下的n-sum[Nx]个节点的时候都会经过L1一次),然后,递归深搜,说是遍历每一个节点,但是也遍历了每一条树支L1,然后累加就好啦!

因为没开long long然后WA了一次。。。。。本题解也,入门向叭。

AC代码如下

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll INF=0x3f3f3f3f;
const ll N=100005;
ll s[N];
ll n; 
ll r1=-1,r2=-1;
vector<ll> G[N];
ll sum[N];
ll sss;
ll maxn;
void init() {
	for(ll i=1;i<=n;i++) {
		s[i]=i;
	}
}
int find_s(ll x) {
	if(x!=s[x]) s[x]=find_s(s[x]);
	return s[x];
}
void union_s(ll x,ll y) {
	x=find_s(x);
	y=find_s(y);
	if(x!=y) s[x]=s[y];
}
ll dfs(ll x,ll pre) {
	ll ans=1;
	for(ll i=0;i<G[x].size();i++) {
		if(G[x][i]==pre) continue;
		ans+=dfs(G[x][i],x);
	}
	return sum[x]=ans;
}
void dfs_1(ll x,ll ss,ll &newN,ll pre) {
	ll x1=ss-sum[x]; //记录最大子树的节点数 
	for(ll i=0;i<G[x].size();i++) { 
		if(G[x][i]==pre) continue;
		x1=max(x1,sum[G[x][i]]); 
	}  
	if(x1<maxn) { 
		maxn=x1;
		newN=x;
	}
	for(ll i=0;i<G[x].size();i++) {
		if(G[x][i]==pre) continue;
		dfs_1(G[x][i],ss,newN,x); 
	}
}
ll dfs_2(ll x,ll pre) {
	ll ans=0;
	for(ll i=0;i<G[x].size();i++) {
		if(G[x][i]==pre) continue;
		//对于每一条树支,被走过的次数就是两边节点数的乘积 
		ans+=sum[G[x][i]]*(sss-sum[G[x][i]]);
		ans+=dfs_2(G[x][i],x);
	} 
	return ans;
}
int main() {
	ios::sync_with_stdio(false);
	cin>>n;
	init();
	for(ll i=1;i<=n-2;i++) {
		ll u,v;
		cin>>u>>v;
		union_s(u,v);
		G[u].push_back(v);
		G[v].push_back(u);
	} 
	for(ll i=1;i<=n;i++) {
		if(s[i]==i) {
			if(r1==-1) r1=i;
			else r2=i;
		}
	}
	//预处理sum数组 
	ll s1=dfs(r1,-1);
	ll s2=dfs(r2,-1);
	sss=s1+s2;
	ll nr1=r1,nr2=r2;
	//对两棵树分别找重心 
	maxn=INF;
	dfs_1(r1,s1,nr1,-1);
	maxn=INF;
	dfs_1(r2,s2,nr2,-1); 
	//连树 
	G[nr1].push_back(nr2);
	G[nr2].push_back(nr1);
	union_s(nr1,nr2);
	//再处理新sum数组 
	dfs(nr1,-1);
	//计算输出 
	cout<<dfs_2(nr1,-1)<<endl;
	return 0; 
}

  • 10
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值