树的重心

定义

对于树上的每一点,计算其所有子树(包括"向上"的那棵子树)中最大的子树节点数,这个值最小的点就是这棵树的重心。 ------OI WiKi

我的理解就是重心是整个物体(在这里是树)的平衡点,为了保持平衡从重心出发到其他点的距离都不会太大而导致失衡。

性质

1.一棵树最少有一个重心,最多有两个重心,若有两个重心,则它们有边相连。
2.树上所有点到某个点的距离和里,到重心的距离和最小,若有两个重心,则其距离和相同。
3.若以重心为根,则所有子树的大小都不超过整棵树的一半。
4.在一棵树上添加或删除一个叶子节点,其重心最多移动到相邻结点。
5.两棵树通过连一条边组合成新树,则新树重心在原来两棵树的重心的连线上。

求解重心

边点无权值
很普通的一棵树,按照定义来求即可。
任选一个节点作为根节点,跑DFS,记录所有子树的大小(包括向上的那一棵)。
这样就得到了每个节点最大子树的节点数,取最小的那个节点即可。
例题:Balancing Act
参考代码:

#include<iostream>
#include<cmath>
#include<cstring>
using namespace std;
const int N=2e4+10;
int ans[N];
int head[N],cnt;
struct {
	int to,next;
}e[2*N];
int n;
void add(int a,int b){
	e[++cnt].to=b;
	e[cnt].next=head[a];
	head[a]=cnt;
	e[++cnt].to=a;
	e[cnt].next=head[b];
	head[b]=cnt;
}
int dfs(int u,int f){
	//printf("%d ",u);
	int num=0,temp;
	int to;
	for(int i=head[u];i;i=e[i].next){
		to=e[i].to;
		if(f==to)continue;
		temp=dfs(to,u);
		num+=temp;
		ans[u]=max(ans[u],temp);
	}
	++num;
	ans[u]=max(ans[u],n-num);
	return num;
}
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		scanf("%d",&n);
		cnt=0;
		memset(ans,0,sizeof(ans));
		memset(head,0,sizeof(head));
		int u,v;
		for(int i=1;i<n;++i){
			scanf("%d%d",&u,&v);
			add(u,v);
		}
		dfs(1,0);
		int ind=1;
		for(int i=1;i<=n;++i){
			if(ans[i]<ans[ind])ind=i;
		}
		printf("%d %d\n",ind,ans[ind]);
	}
	return 0;
}

多测不清空,爆零两行泪

点有权值,边无权值
例题:H - Hoist the Tree 感觉这个链接随时可能会炸额
首先以1为根节点算一遍代价,然后就是代价的转移。相邻节点的代价是可以直接转移的。

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
#define ll long long 
int dep[N],w[N],head[N],cnt;
ll size[N];
struct{
    int to,next;
}e[2*N];
void add(int u,int v){
    e[++cnt].to=v;
    e[cnt].next=head[u];
    head[u]=cnt;
    e[++cnt].to=u;
    e[cnt].next=head[v];
    head[v]=cnt;
}
ll ans;
void dfs(int u,int f){
    int to;
    for(int i=head[u];i;i=e[i].next){
        to=e[i].to;
        if(to==f)continue; 
		dep[to]=dep[u]+1;
		dfs(to,u);
		size[u]+=size[to];  
    }
    ans+=1ll*dep[u]*w[u];
    size[u]+=w[u];
}
void work(int u,int f,ll pre){
    int v;
    ans=min(ans,pre);
    for(int i=head[u];i;i=e[i].next){
        v=e[i].to;
        if(v==f)continue;
        work(v,u,pre+size[1]-2*size[v]);//这里是转移方程,下面代价减掉(-size[v]),上面代价加上(+size[1]-size[v])
    }
}
int main(){
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;++i)scanf("%d",&w[i]);
    int u,v;
    for(int i=1;i<n;++i)scanf("%d%d",&u,&v),add(u,v);
    dfs(1,0);
    work(1,0,ans);
   // for(int i=1;i<=n;++i)printf("%d%c",size[i],i==n?'\n':' ');
    //for(int i=1;i<=n;++i)printf("%d%c",dep[i],i==n?'\n':' ');
    printf("%lld",ans);
    return 0;
}

参考

【朝夕的ACM笔记】树上问题-树的重心

posted @ 2022-07-10 16:28  何太狼  阅读(117)  评论(0编辑  收藏  举报