标签:遍历 etc 倒序 pac pre 要求 str 图片 sub
Update:有问题请私信我,我会在4848小时内回复。
最近公共祖先问题:简称LCALCA(Least Common AncestorsLeastCommonAncestors),指的给出一棵有根多叉树,询问x,yx,y的最近公共祖先。
例如上图中,44和33的LCALCA是22,88和33的LCALCA是11。
如果x,yx,y在树中的深度不同,那么先将xx和yy跳到同一深度,然后直接枚举xx和yy的父亲,爷爷,爷爷的父亲...是否相等
例如,x=6,y=11x=6,y=11。
y:11->9y:11?>9
跳到同一深度
x:6->4->2->1x:6?>4?>2?>1
y:9->8->7->1y:9?>8?>7?>1
逐步向上跳,直到相遇。
妥妥的TLE。
前置知识:十进制整数的二进制唯一分解性质(自行百度)
首先,设f[k][i]f[k][i]表示kk的2^i2i倍祖先。如果这个节点不存在,那么我们可以将它设为00。
设rr为x,yx,y相遇要走的层数。(当然,我们并不知道rr)在朴素做法中,我们是一层一层向上跳。rr一定可以分解为一个二进制数。在rr的第jj个二进制位为1的时候,x,yx,y一起向上跳,就可以相遇了。
例如,r=5r=5。(5)_{10}=(1001)_{2}(5)10?=(1001)2?。所以,x,yx,y要先跳2^323层,再跳2^020层。如果再往上跳,那么x,yx,y就会一直相等了,而此时找到的,就并不是最近的公共祖先了。
void dfs(int k,int father)//记录父亲节点
{
dep[k]=dep[father]+1;
for(register int i=1;i<=20;++i)//20要进行实际计算
f[k][i]=f[f[k][i-1]][i-1];//(注:这里^是次方的符号)记录2^x倍祖先,方便二进制拆分。很显然:2^(x-1)+2^(x-1)=2^x 如果2^x倍祖先不存在,那么为0(我们不用特判)
for(register int i=head[i];i;i=edge[i].next)//邻接表遍历
{
int v=edge[i].to;
if(v==father)//如果是父亲,那么就不能访问
continue;
f[v][0]=k; //2的零次方等于1,即父亲节点 dfs(v,k);//继续往下搜索
}
return;
}
if(dep[x]>dep[y])std::swap(x,y);
前置知识
二进制唯一分解性质将xx的深度调整与yy相同。
for(register int i=20;i>=0;i--){
if(dep[f[x][i]]>=dep[y])x=f[x][i];//这个语句的意思是,如果还没有到y的深度,就往上跳。如果要往上面跳五层才相遇,那么x就会在i=3和i=0的时候跳。(二进制拆分)
if(x==y)return x;
//如果x,y跳到了同一层,并且相等,那么LCA就是x了
}
for(register int i=20;i>=0;i--)
if(f[x][i]!=f[y][i])//如果不相遇才跳,相遇了的话我们就不能确定这个祖先是不是最近的啦
x=f[x][i],y=f[y][i];
f[x][0]
或者f[y][0]
就好啦
return f[x][0];
最后附上【模板】最近公共祖先(LCA)完整的代码,希望大家有收获哦~
代码的正确打开方式:无视掉register
,inline
和read()
。
# include <bits/stdc++.h>
# define rr register//使用了一个宏定义
const int N=500001;
int dep[N];//记录深度
int f[N][21];//记录2^x倍祖先
int head[N];//图论邻接表,自行百度
struct Edge
{
int to,next;
}edge[N<<1];//邻接表存树
int n,m,sum,s;//n,m,s如题目所述。sum是邻接表的内容
inline void add(int x,int y)//邻接表建立边
{
edge[++sum].to=y;
edge[sum].next=head[x];
head[x]=sum;
edge[++sum].to=x;
edge[sum].next=head[y];
head[y]=sum;//记得是双向边
return;
}
inline int read()//快速读入
{
int res,f=1;
char c;
while((c=getchar())<‘0‘||c>‘9‘)
if(c==‘-‘)f=-1;
res=c-48;
while((c=getchar())>=‘0‘&&c<=‘9‘)
res=res*10+c-48;
return res*f;
}
void first_work(int k,int father)
{
dep[k]=dep[father]+1;//深度是父亲+1
for(rr int i=1;i<=20;++i)//预处理他的1~20倍祖先
f[k][i]=f[f[k][i-1]][i-1];//k的2^(x-1)倍祖先的2^(x-1)倍祖先就是k的2^x倍祖先
for(rr int i=head[k];i;i=edge[i].next)//邻接表遍历相连的边
{
if(edge[i].to==father)continue;//如果是它的父亲就不管,因为我们要搜索他的子孙
f[edge[i].to][0]=k;//它的2^0倍(1倍)祖先就是k
first_work(edge[i].to,k);//往下搜
}
return;//规范的写法
}
inline int lca(int x,int y)//找lca
{
if(dep[x]<dep[y])std::swap(x,y);//让x深度较大
for(rr int i=20;i>=0;i--){//记住,是倒序。因为我们要慢慢逼近
if(dep[f[x][i]]>=dep[y])x=f[x][i];//如果还能往上跳,就跳
if(x==y)return x;//x,y如果跳到了一层,并且相遇了,那么lca就是x
}
for(rr int i=20;i>=0;i--)
if(f[x][i]!=f[y][i])//如果不相等,也就是还没有到lca的位置,我们就往上跳
x=f[x][i],y=f[y][i];//往上跳
return f[x][0];//因为往上跳的时候我们要求不相遇,所以只会跳到lca的底下一层。我们就要返回x的父亲或者y的父亲
}
int main()
{
n=read();
m=read();
s=read();
for(rr int i=1;i<n;i++)
{
int x=read(),y=read();
add(x,y);
}
first_work(s,0);//小技巧:s没有祖先,就把它的祖先设为0
for(rr int i=1;i<=m;i++)//m次询问
{
int x,y;
x=read();
y=read();
printf("%d\n",lca(x,y));//求出lca并输出就好啦
}
return 0;//代码规范
}
最后再说一句:请读者指出此篇文章的不足之处!谢谢!不懂的可以在洛谷上面私信我!作者地址
标签:遍历 etc 倒序 pac pre 要求 str 图片 sub
原文地址:https://www.cnblogs.com/liuzongxin/p/10364484.html