树的重心
也有许多的信息是自底向上进行统计的,比如每个节点x为根的子树大小
对于一个节点x,如果我们把它从树中删除,那么原来的一棵树可能会分成若干个不相连的部分,其中每一部分都是子树,设maxpart函数表示在删除节点x后产生的子树中,最大的一颗的大小,那么x就是重心
void dfs(int x)
{
v[x]=1;
size[x]=1;//假设子树的大小
int maxpart=0;//假设,预处理
for(int i=head[x];i;i=next[i])
{
int y=edge[i].ver;
if(v[y]) continue;
dfs(y);
size[x]+=size[y];
maxpart=max(maxpart,size[y]);//获取最大的子树
}
maxpart=max(maxpart,n-size[x]);//删除这个点最大值
if(maxpart<ans)//这里为什么记录小一点的?
//
{
ans=maxpart;//记录对应长度
pos=x;//记录重点
}
}
树的中心
圆的中心是圆心,树的中心呢?
树的中心类似与圆心,是该点到树中其他点的距离最远距离最小,同样,树的中心可能有很多个
树的中心满足如下性质:
1.它一定在树的直径上,并且到直径两端距离差不超过1
2.即使树有很多条直径,但是树的中心最多只有两个
假设直径长度为R,我们在x~y的路径上找到与x距离R/2的点,如果R/2小于x到z的路径,那么中心就在x到z的路径上,否则就在z到y的路径上 ,类似于二分思想
int work()
{
int R=de[x]+de[y]-2*de[z];
if(de[x]-de[z]>R/2)
{
for(int i=1;i<=R/2;i++)
{
x=fa[x];
}
return x;
}
else
for(int i=1;i<=R-R/2;i++)
y=fa[y];
return y;
}
LCA
最近公共祖先,祖先就是指一个点的父亲,字面意思就可以理解,那么公共祖先就是两个点共有的祖先,最近的那一个叫做LCA
两个点的LCA只有一个
LCA的求法有四种,tajan,RMQ,树上倍增,树链剖分
tarjan只能离线求
RMQ的长度要翻倍
通常情况要用倍增和树剖,不过树剖是省队或者NOI才学的东西,我太菜了没有这么时间精力,目前不想学
一、暴力求解,两个点不断地向根的方向移动来求出
1.先求出每一个点深度
2.询问两个点是否重合,如果重合就求出来了LCA
3.不然挑选一个深度大的移动
int de[100],fa[100];
void dfs(int x,int fath)
{//获取层数
fa[x]=fath;
de[x]=de[fath]+1;
for(int i=fa[x];i;i=next[i])
{
if(edge[i].var!=fath)
dfs(edge[i].var,x);
}
}
int lca(int x,int y)
{
while(x!=y)//直至两个点重合
{
if(de[x]>=de[y]) x=fa[x];//上移
else y=fa[y];
}
return x;
}
二、倍增,这个算法是对一步一步往上找的一个优化,向上移动的步数为2的次幂步数
1.先求出一个倍增数组anc[maxn][maxlog]其中,maxlog就是指2x>=maxdeep也就是最大的一个限制,我们规定anc[i][j]是节点i向上走2j步到达的节点。所以我们规定根节点的父亲就是自己,对于一个方程“anc[i][j]=anc[anc[i][j-1]][j-1]”
2.事先我们让x处于深一点的状态(否则进行交换),然后如果x所在的层数要是大于y的层数,就一直将x往上跳,直至两个点层数相同
3.如果这两个点重合,那么说明LCA求出来了
4.否则,就对x往上跳2i步和y往上跳2i进行一个比较,直至两个点重合,但是最后还得再次向上走一个
int de[100],anc[100][100];
void dfs(int x,int fa)
{
de[x]=de[fa]+1;
anc[x][0]=fa;//向上走一步为自己的父亲
for(int i=1;i<maxlog;i++)
{
anc[x][i]=anc[anc[x][i-1]][i-1];
for(int i=fath[x];i;i=next[i])
{
if(edge[i].var!=fa) dfs(edge[i].var,x);
}
}
}
int lca(int x,int y)
{
if(de[x]<de[y]) swap(x,y);//令x较大
for(int i=maxlog-1;~i;i--)
{
if(de[anc[x][i]]>=de[y]) x=anc[x][i];
}
if(x==y) return x;//重合
for(int i=maxlog-1;~i;--i)
{
if(anc[x][i]!=anc[y][i]) x=anc[x][i],y=anc[y][i];//一直往上跳
}
return anc[x][0];
}
树的直径
树中两点不重复经过的边和点叫做路径,路径长度称为距离
树的直径是树中两点最长路径,链接这两个点的路径叫做树的最长链,也就是说直径是一个数值概念,树的直径有很多条
树的直径满足的性质就是:
1.如果有多条直径,那么所有的直径都有一个公共点,也就是穿过的点
2.直径的两端肯定是叶子节点
那么我们如何求出来直径呢?
解法一:暴力,两边DFS
第一遍DFS求出距离某个点最远的节点x
第二遍DFS求出距离x点最远的点y
x到y的简单路径,就是树的直径
为了找出来距离某个节点最远的点,这棵树可以看作无根树,一个节点连向父亲边也要存入 邻接表
int x,y,de[x];
void getde(int x,int fa)
{
de[x]=de[fa]+1;//求取距离
for(int i=head[x];i;i=next[i])
{
if(edge[i].var!=fa)
getda(edge[i].var,x);//递归儿子
}
}
void work()
{
getde(1,0);//这里就是建树
x=1;//假设1号节点
for(int i=2;i<=n;i++)
if(de[i]>de[x] x=i;//找出最远的点
getde(x,0)
y=1;//假设1号节点
for(int i=2;i<=n;i++)
if(de[i]>de[y]) y=i;
cout<<x<<" "<<y;
cout<<de[y]<<endl;
}
解法二:LCA
我们通过LCA来求取DFS过程中对于每一个点,我们考虑以它为LCA的可能的直径,我们只需要维护最长链最次链
枚举每一个点,以这个点的最长最次,然后取最大值
int l=0;
int dfs(int x,int fa)
{
int mx,mmx=0;//最长链,最次链
for(int i=head[x];i;i=next[i])//枚举每一个点作为LCA
{
if(edge[i].var!=fa)
{
int tmp=dfs(edge[i].var,x);//递归儿子
if(tmp>mx) mmx=mx,mx=tmp;
else if(tmp>mmx) mxx=tmp;
}
}
l=max(l,mmx+mx);
return l;
}