以下是一些关于树的直径和重心的题目,为本蒟蒻在上网课时所整理。
一些知识
树的直径
定义:树上最长链。
推论,前提是边权非负:
- 一棵树若存在多条直径,则必然相交于同一点。
- 任意一点的最远端是树上直径上的一个端点。
- 若多条直径只有一个公共点,必定互相平均分割。
树的重心
也叫树的质心。
定义:对于一棵树n个节点的无根树,找到一个点,使得把树变成以该点为根的有根树时,最大子树的结点数最小。换句话说,删除这个点后最大连通块(一定是树)的结点数最小。
推论:
- 树中所有点到某个点的距离和中,到重心的距离和是最小的,如果有两个距离和,他们的距离和一样。
- 把两棵树通过一条边相连,新的树的重心在原来两棵树重心的连线上。
- 一棵树添加或者删除一个节点,树的重心最多只移动一条边的位置。
- 一棵树最多有两个重心,且相邻。
VJ 旅游规划(csapc)
题意
给定一棵树,节点数 ,边权均为 ,求直径上的点,从小到大输出。
点击查看题解
题解
我们有两种解法。
Solution 1
进行 遍搜索(这里用的 遍 dfs)。
第一遍:任意选一点(选 号点)开始 dfs,求出距离此点最远的点,那样一个直径的端点就被确定了。任意一点的最远端是树上直径上的一个端点。
第二遍:从这个端点 dfs,可求这个直径的另一端点。
第三遍:从任一端点 dfs,设当前 dfs 到的点是 ,深度是 ,从 的子树方向的最远点到 的距离是 ,则如果 直径,则 就是直径上的点。
第四遍:从另一个端点 dfs,重复以上过程。
为什么要进行第四遍:考虑下面的图。
如果我们步骤 是从图中蓝色标记 的地方开始 dfs,它“两旁”的点就不会计入答案。通过步骤 ,可以计入答案。
如果存在负边权,则此种方法无法得出正确答案。
Solution 2
进行 遍 dp。
第一遍:向“下” dp,即 dp 一个点(记为 root)来自其子树方向的最长链和次长链。
第二遍:向“上” dp,即 dp 一个点(记为 son)来自其父亲方向的最长链和次长链。注意不要使得父亲方向的最长链或次长链和来自 son 的子树方向的最长链和次长链重合。
此法任何边权可解。
代码
两个命名空间表示两种做法。
#include<bits/stdc++.h>
using namespace std;
namespace Solution_1
{
const int N=200000+10;
struct edge{int nxt,to,len;}e[N*2];
int n,head[N],cnt=0,jud,one,two,d[N],s[N],len=0,ans[N];
void add(int x,int y,int len){e[++cnt]=(edge){head[x],y,len};head[x]=cnt;}
void init()
{
memset(d,0,sizeof(d));
memset(s,0,sizeof(s));
jud=-1;
}
void dfs(int root,int dad,int &var)
{
if(jud<d[root]) jud=d[root],var=root;
for(int i=head[root];i;i=e[i].nxt)
{
int son=e[i].to;
if(son==dad) continue;
d[son]=d[root]+e[i].len;
dfs(son,root,var);
if(len) s[root]=max(s[son]+e[i].len,s[root]);
}
if(len) if(s[root]+d[root]==len) ans[root]++;
}
void yzmain()
{
int t1,t2;
scanf("%d",&n);
for(int i=1;i<=n-1;i++)
{
scanf("%d%d",&t1,&t2);
add(t1,t2,1);add(t2,t1,1);
}
init();dfs(0,-1,one);
init();dfs(one,-1,two);
len=jud;
init();dfs(one,-1,t1);
init();dfs(two,-1,t2);
for(int i=0;i<n;i++)
if(ans[i]) printf("%d\n",i);
return;
}
}
namespace Solution_2
{
const int N=200000+10,First=1,Second=2;
struct edge{int nxt,to,len;}e[N*2];
int n,head[N],cnt=0,f[N][10],p[N][10],ret[N],dia=0;
void add(int x,int y,int len){e[++cnt]=(edge){head[x],y,len};head[x]=cnt;}
void update(int root,int len,int point)
{
if(f[root][First]<len)
f[root][Second]=f[root][First],
p[root][Second]=p[root][First],
f[root][First]=len,
p[root][First]=point;
else if(f[root][Second]<len)
f[root][Second]=len,
p[root][Second]=point;
}
int dfsdown(int root,int dad)
{
for(int i=head[root];i;i=e[i].nxt)
{
int son=e[i].to;
if(son==dad) continue;
update(root,dfsdown(son,root)+e[i].len,son);
}
return f[root][First];
}
void dfsup(int root,int dad,int len)
{
update(root,len,dad);
dia=max(dia,f[root][First]+f[root][Second]);
for(int i=head[root];i;i=e[i].nxt)
{
int son=e[i].to;
if(son==dad) continue;
if(son!=p[root][First]) dfsup(son,root,e[i].len+f[root][First]);
else dfsup(son,root,e[i].len+f[root][Second]);
}
}
void yzmain()
{
int t1,t2;
scanf("%d",&n);
for(int i=1;i<=n-1;i++)
{
scanf("%d%d",&t1,&t2);
add(t1,t2,1);add(t2,t1,1);
}
dfsdown(0,-1);dfsup(0,-1,-1);
for(int i=0;i<n;i++)
if(f[i][First]+f[i][Second]==dia) printf("%d\n",i);
return;
}
}
int main()
{
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
Solution_CE::yzmain();
return 0;
}
P4408 [NOI2003] 逃学的小孩
题意
给一棵无根树,选出 ,, 三个点使得 最大,并保证 。
点击查看题解
题解
贪心,找直径(设直径端点为 ,),枚举 点,则答案为 。这里 是图的点集。
证明请见题解 题解 P4408 【NOI2003 逃学的小孩】。
代码
#include<bits/stdc++.h>
using namespace std;
namespace _yz
{
const int N=200000+10;
struct edge{int nxt,to,len;}e[N*2];
int n,m,head[N],cnt=0,one,two;
long long jud,d1[N],d2[N],s[N],ans=0,len=0;
void add(int x,int y,int len){e[++cnt]=(edge){head[x],y,len};head[x]=cnt;}
void init(long long (&d)[N])
{
memset(d,0,sizeof(d));
memset(s,0,sizeof(s));
jud=0;
}
void dfs(int root,int dad,int &var,long long (&d)[N])
{
if(jud<d[root]) jud=d[root],var=root;
for(int i=head[root];i;i=e[i].nxt)
{
int son=e[i].to;
if(son==dad) continue;
d[son]=d[root]+e[i].len;
dfs(son,root,var,d);
if(len) s[root]=max(s[son]+e[i].len,s[root]);
}
}
void yzmain()
{
int t1,t2,t3;
scanf("%d%d",&n,&m);
for(int i=1;i<=n-1;i++)
{
scanf("%d%d%d",&t1,&t2,&t3);
add(t1,t2,t3);add(t2,t1,t3);
}
init(d1);dfs(1,-1,one,d1);
init(d1);dfs(one,-1,two,d1);
len=jud;
init(d1);dfs(one,-1,t1,d1);
init(d2);dfs(two,-1,t2,d2);
for(int i=1;i<=n;i++)
ans=max(ans,min(d1[i],d2[i])+len);
printf("%lld\n",ans);
return;
}
}
int main()
{
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
_yz::yzmain();
return 0;
}
P1395 会议
题意
给出一棵树,节点数 ,求其重心(序号更小的那个)和每个点距离重心的距离和。
点击查看题解
题解
求重心部分:通过重心的定义(删除一个点后最大连通块的结点数最小的点是重心)可以写出以下 dfs 程序。
使用两个数组 st[i]
和 tal[i]
分别表示删除第 号点的最大子树,以第 号点为子树根的大小。
在枚举算符中,st[i]
不会更新到来自 点的父亲方向的子树,所以要用 即父亲方向的子树大小更新 。
计算距离部分:设 num[i]
和 d[i]
分别表示以第 号点为子树根的子树节点数,子树所有节点到第 号点的距离和。若一个点为 root,其儿子为 son,则 son 子树所有节点到 root 的距离和 son 子树所有节点到 son 的距离和 son 子树所有节点数 root 到 son 的距离。
代码
#include<bits/stdc++.h>
using namespace std;
namespace _yz
{
const int N=50000+10;
struct edge{int nxt,to,len;}e[N*2];
int n,head[N],cnt=0,tal[N],st[N],size,ans,d[N],num[N];
void add(int x,int y,int len){e[++cnt]=(edge){head[x],y,len};head[x]=cnt;}
void dfs(int root,int dad)
{
for(int i=head[root];i;i=e[i].nxt)
{
int son=e[i].to;
if(son==dad) continue;
tal[son]=e[i].len;
dfs(son,root);
st[root]=max(st[root],tal[son]);
tal[root]+=tal[son];
}
st[root]=max(st[root],n-tal[root]);
}
void dis(int root,int dad)
{
for(int i=head[root];i;i=e[i].nxt)
{
int son=e[i].to;
if(son==dad) continue;
num[son]=1;
dis(son,root);
d[root]+=d[son]+num[son]*e[i].len;
num[root]+=num[son];
}
}
void yzmain()
{
int t1,t2;
scanf("%d",&n);
for(int i=1;i<=n-1;i++)
{
scanf("%d%d",&t1,&t2);
add(t1,t2,1);add(t2,t1,1);
}
dfs(1,1);size=st[1];ans=1;
for(int i=2;i<=n;i++)
{
if(size>st[i])
size=st[i],ans=i;
}
dis(ans,ans);
printf("%d %d",ans,d[ans]);
return;
}
}
int main()
{
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
_yz::yzmain();
return 0;
}
P1364 医院设置
题意
给出一棵二叉树,节点数 ,点有点权,求其每个点距离重心的距离和。
点击查看题解
代码
这个题当作有点权求重心的模板题吧。无需题解。
#include<bits/stdc++.h>
using namespace std;
namespace _yz
{
const int N=100+10;
struct edge{int nxt,to;}e[N*2];
int n,head[N],cnt=0,tal[N],st[N],d[N],num[N],sum=0;
void add(int x,int y){e[++cnt]=(edge){head[x],y};head[x]=cnt;}
void dfs(int root,int dad)
{
tal[root]=num[root];
for(int i=head[root];i;i=e[i].nxt)
{
int son=e[i].to;
if(son==dad) continue;
dfs(son,root);
st[root]=max(st[root],tal[son]);
tal[root]+=tal[son];
}
st[root]=max(st[root],sum-tal[root]);
}
void dis(int root,int dad)
{
for(int i=head[root];i;i=e[i].nxt)
{
int son=e[i].to;
if(son==dad) continue;
dis(son,root);
d[root]+=d[son]+num[son];
num[root]+=num[son];
}
}
void yzmain()
{
int t1,t2,t3,size,ans;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d%d%d",&t1,&t2,&t3);
num[i]=t1;sum+=t1;
if(t2) add(i,t2),add(t2,i);
if(t3) add(i,t3),add(t3,i);
}
dfs(1,1);size=st[1];ans=1;
for(int i=2;i<=n;i++)
{
if(size>st[i])
size=st[i],ans=i;
}
dis(ans,ans);
printf("%d",d[ans]);
return;
}
}
int main()
{
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
_yz::yzmain();
return 0;
}
X1577 【例 3】数字转换
题意
如果一个数 的约数和 (不包括 本身)比 小,那么 和 可以互相转换。例如 可以变为 , 可以变为 。给定正整数 ,限定所有数字转换在不超过 的正整数范围内进行,求不断进行数字转换且不出现重复数字的最多转换步数。。
点击查看题解
题解
刚看题,感觉这道题和树没有什么关系。不过我们画一下数字转换的路径,这竟然是棵树!
下图为 时的情况:
如果设定 是 的前驱,则由于对于每个 ,它的 值是确定的,这就保证 是 唯一的前驱;且 ,这就保证不会出现环。因此数字转换的路径是一棵无根树。
接下来就很显然了:求这棵树的直径大小。
首先预处理出每个 的 值,再连边,即可求直径。
代码
#include<bits/stdc++.h>
using namespace std;
namespace _yz
{
const int N=50000+10;
struct edge{int nxt,to;}e[N*2];
int n,head[N],cnt=0,dis[N],pos,ans;
queue<int>q;
void add(int a,int b)
{
e[++cnt]=(edge){head[a],b};
head[a]=cnt;
}
void bfs(int st)
{
memset(dis,-1,sizeof(dis));
pos=0,ans=-1;
q.push(st);dis[st]=0;
while(!q.empty())
{
int now=q.front();q.pop();
if(dis[now]>ans) ans=dis[now],pos=now;
for(int i=head[now];i;i=e[i].nxt)
{
int to=e[i].to;
if(dis[to]==-1)
dis[to]=dis[now]+1,
q.push(to);
}
}
}
void yzmain()
{
int tmp;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
tmp=0;
for(int j=1;j*j<=i;j++)
{
if(i%j) continue;
tmp+=j;
if(j!=i/j) tmp+=i/j;
}
tmp-=i;
if(i>tmp&&tmp!=0) add(i,tmp),add(tmp,i),printf("%d: %d\n",i,tmp);
}
bfs(1);bfs(pos);//这里使用 bfs 求直径。
printf("%d\n",ans);
return;
}
}
int main()
{
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
_yz::yzmain();
return 0;
}
本文作者:Yurchiu
本文链接:https://yz-hs.github.io/68f96485e7fc/
版权声明:本博客中所有原创文章除特别声明外,均允许规范转载,转载请注明出处。所有非原创文章,按照原作者要求转载。