当前位置: 首页 > news >正文

数据结构与算法:树的重心

前言

现在看这些题感觉格外善良,比cf那些抽象guess猜结论题好多了……

一、树的重心及性质

树的重心有以下三种定义,求出来的点是一样的:

(1)以某个节点为根时,最大子树的节点数最少,那么这个节点就是重心。

(2)以某个节点为根时,每棵子树的节点数不超过总节点数的一半,那么这个节点就是重心。

(3)以某个节点为根时,所有节点都走向该节点的总边数最少,那么这个节点就是重心。

补充的性质:

(4)一棵树最多有两个重心,如果有两个重心,那么两个重心一定相邻。

(5)如果在树上增加或删除一个叶节点,转移后的重心最多移动一条边。

(6)如果把两棵树连起来,那么新树的重心一定在原来两棵树重心的路径上。

(7)若树上的边权都为正数,不管边权如何分布,让所有节点都走向一个点时,走向重心的总距离和最小。

二、树的重心的求法

1.第一种——Balancing Act

poj这个评测真的依托……

第一种求解方式是基于树的重心的第一个定义,即最大子树的节点数最少。

#include<iostream>
#include<vector>
using namespace std;/*   /\_/\
*   (= ._.)
*   / >  \>
*/const int MAXN=5e4+5;vector<vector<int> >g(MAXN);//sz[u]:u的子树节点个数
vector<int>sz(MAXN);int n;//最大子树的节点数
int best;
//重心
int center;void dfs(int u,int fa)
{sz[u]=1;//以u为根节点时,最大的子树的节点个数int maxx=0;for(int i=0;i<g[u].size();i++){int v=g[u][i];if(v!=fa){dfs(v,u);sz[u]+=sz[v];maxx=max(maxx,sz[v]);}}//u上方部分的节点数maxx=max(maxx,n-sz[u]);//节点数更少或节点编号更小if(maxx<best||(maxx==best&&u<center)){best=maxx;center=u;}
}void build()
{for(int i=1;i<MAXN;i++){g[i].clear();sz[i]=0;}best=1e9;
}void solve()
{build();cin>>n;for(int i=0,u,v;i<n-1;i++){cin>>u>>v;g[u].push_back(v);g[v].push_back(u);}dfs(1,0);cout<<center<<" "<<best<<endl;
}signed main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;cin>>t;while(t--){solve();    }return 0;
}

这个方法是不是只能求一个重心……

方法就是直接去dfs,统计子树大小。然后就需要求出以当前节点为重心时各个子树大小的最大值,那么就是先在所有孩子节点子树中取最大,然后父亲节点那部分的子树大小就是n减去当前节点的子树大小。若这个最大值能把全局最大值更新得更小,就直接更新当前树的重心。

2.第二种——树的重心

还是洛谷的评测好()

第二种求法是基于第二个定义,即每棵子树的节点数不超过总节点数的一半。

#include <bits/stdc++.h>
using namespace std;/*   /\_/\
*   (= ._.)
*   / >  \>
*/typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;const int MAXN=5e4+5;vector<vector<int>>g(MAXN);//sz[u]:u的子树节点个数
vector<int>sz(MAXN);//maxsub[u]:以u为根节点时,最大的子树的节点个数
vector<int>maxsub(MAXN);int n;void dfs(int u,int fa)
{sz[u]=1;//以u为根节点时,最大的子树的节点个数maxsub[u]=0;for(auto v:g[u]){if(v!=fa){dfs(v,u);sz[u]+=sz[v];maxsub[u]=max(maxsub[u],sz[v]);}}//u上方部分的节点数maxsub[u]=max(maxsub[u],n-sz[u]);
}void solve()
{cin>>n;for(int i=0,u,v;i<n-1;i++){cin>>u>>v;g[u].push_back(v);g[v].push_back(u);}dfs(1,0);vector<int>ans;for(int i=1;i<=n;i++){if(maxsub[i]<=n/2){ans.push_back(i);}}for(auto x:ans){cout<<x<<" ";}cout<<endl;
}signed main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;//cin>>t;while(t--){solve();    }return 0;
}

这个方法就可以求出多个树的重心了。

这个题因为需要最后比较每个节点为重心时最大子树的个数,所以可以考虑定义maxsub数组,在dfs时把每个节点的最大子树大小存起来,最后再把小于等于n/2的节点都加入即可。

三、题目

1.Great Cow Gathering G

#include <bits/stdc++.h>
using namespace std;/*   /\_/\
*   (= ._.)
*   / >  \>
*/typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;const int MAXN=1e5+5;int n;
int sum;
vector<int>a(MAXN);
vector<vector<pii>>g(MAXN);int best=1e9;
int center;//sz[u]:从1节点开始dfs,u节点子树上牛的总数
vector<int>sz(MAXN);//path[u]:从重心开始dfs,到u节点的路程
vector<int>path(MAXN);void solve()
{cin>>n;for(int i=1;i<=n;i++){cin>>a[i];sum+=a[i];}for(int i=1,u,v,w;i<=n;i++){cin>>u>>v>>w;g[u].push_back({v,w});g[v].push_back({u,w});}//找重心auto dfs1=[&](auto &&self,int u,int fa)->void{sz[u]=a[u];int maxx=0;for(auto [v,w]:g[u]){if(v!=fa){self(self,v,u);sz[u]+=sz[v];maxx=max(maxx,sz[v]);}}maxx=max(maxx,sum-sz[u]);if(maxx<best){best=maxx;center=u;}};dfs1(dfs1,1,0);//设置路径长度auto dfs2=[&](auto &&self,int u,int fa)->void{for(auto [v,w]:g[u]){if(v!=fa){path[v]=path[u]+w;self(self,v,u);}}};path[center]=0;dfs2(dfs2,center,0);ll ans=0;for(int i=1;i<=n;i++){//让这个点的牛走过去ans+=1ll*a[i]*path[i];}cout<<ans<<endl;
}signed main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;//cin>>t;while(t--){solve();    }return 0;
}

根据上述性质,肯定是让所有牛都聚集到树的重心上。那么就需要先dfs出树的重心,这里用的是第一种方法。在求出重心后,还需要再dfs一遍求出每个节点到重心的路径和,最后再遍历一遍让每个节点的牛走过去即可。

2.C. Link Cut Centroids

当年的题多么的善良……

#include <bits/stdc++.h>
using namespace std;/*   /\_/\
*   (= ._.)
*   / >  \>
*/typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;void solve()
{int n;cin>>n;vector<vector<int>>g(n+1);for(int i=0,u,v;i<n-1;i++){cin>>u>>v;g[u].push_back(v);g[v].push_back(u);}vector<int>maxsub(n+1);vector<int>sz(n+1);auto dfs=[&](auto &&self,int u,int fa)->void{sz[u]=1;for(auto v:g[u]){if(v!=fa){self(self,v,u);sz[u]+=sz[v];maxsub[u]=max(maxsub[u],sz[v]);}}maxsub[u]=max(maxsub[u],n-sz[u]);};dfs(dfs,1,0);vector<int>center;for(int i=1;i<=n;i++){if(maxsub[i]<=n/2){center.push_back(i);}}if(center.size()==1){cout<<1<<" "<<g[1][0]<<endl;cout<<1<<" "<<g[1][0]<<endl;return ;}int u=center[1];int fa=center[0];int ru=0;int rv=0;auto dfs2=[&](auto &&self,int u,int fa)->void{if(g[u].size()==1){if(ru==0){ru=fa;rv=u;}return ;}for(auto v:g[u]){if(v!=fa){self(self,v,u);}}};dfs2(dfs2,u,fa);cout<<ru<<" "<<rv<<endl;cout<<fa<<" "<<rv<<endl;
}signed main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;cin>>t;while(t--){solve();    }return 0;
}

这个题的题意就很直白了,哪像现在的cf读题就得读半天,还得结合样例才能读懂……

首先肯定得求出树的重心,那么这里就得用第二种方法求了,若只有一个重心,那么直接随便找一条边断开再连上即可。而若有两个重心,那么根据重心的性质,这两个重心一定相连,且删除一个叶子后重心最多移动一条边。那么就可以考虑从一个重心开始,设置另一个重心为其父节点,然后删除一个这个重心子树上的叶节点,那么这个重心就会移动到父节点的那个重心上,最后再直接把这个节点连到唯一的重心上即可。

总结

加油加油!!

END

http://www.dtcms.com/a/615343.html

相关文章:

  • JavaScript 中,原型链的**最顶端(终极原型)只有一个——`Object.prototype`
  • 建筑招聘网站哪个好做钢材生意选什么网站
  • 防城港做网站用服务器做网站空间
  • 2025大学生数学竞赛1-2(非数学类)
  • 做类似淘宝的网站要多少钱凡科网站怎么做链接头像logo
  • 做个自己的网站需要多少钱wordpress登录400错误
  • 计算机理论学习Day20(加更)
  • 民治做网站做团购网站需要多少钱
  • arm架构上搭建第一个区块链网络FISCO BCOS
  • MM32F0144芯片ADC电压采样策略详解
  • 自己做的网站怎么取sql数据库一站式网站搭建
  • 网页C语言在线编译 | 提供方便快捷的C语言编程环境
  • 网站如何屏蔽中国ip网页开发工具软件
  • VMware-ubuntu网络配置
  • 东风地区网站建设价格国家免费培训学校
  • 【C语言】贪吃蛇游戏设计思路深度解析:从零开始理解每个模块
  • 《Three.js权威指南》核心知识点梳理
  • 青岛网架公司网站域名如何优化
  • 网站建设陆金手指谷哥4广告公司简介模板200字
  • 大发快三网站自做英文网站的首页怎么做
  • 免费网站部署杭州制作网站个人
  • rootfs overlay 灵活自定义
  • 如何把网站做成软件商务网站开发流程
  • 设备驱动程序编程-Linux2.6.10-kdb安装
  • 怎么看别的网站是那个公司做的服装设计最好的出路
  • 免费网站站盐城建设厅网站设计备案
  • 卡尔曼学习笔记
  • seo导航站php网站费用
  • 建设网站收废品做网站找那些公司
  • 信阳企业网站建设公司网上做衣服的网站有哪些