其实LCA算法的理解也没那么麻烦
tarjan
这是一棵树,我们不难发现,9和12的公共祖先是3,且当我们走到12时,9一定被访问过了,3也一定被访问过了
然后tarjan的大脑就发现了一个问题:如果说我们可以知道访问过的点的祖先,那么图中,我们在dfs到节点12时不就可以直接认为9和12的公共祖先是在9之后遍历到的深度最小的点(即3)了吗?
(其实到了这一步,我们就可以发现两种做法,一种使用RMQ,另一种是tarjan,RMQ暂且不提)
因此,我们可以使用并查集来将已遍历过的点归到他们的祖先上,然后将问题存储起来,若问题中的两个节点都被访问过了,那么我们就可以直接返回先被遍历的那个节点的父节点即可
这其实就是tarjan的基本思想
本人自己打的tarjan如下,具体细节都有解释(luoguP3379码风奇特的奇特)
#include<bits/stdc++.h>
using namespace std;
#define fre freopen("datain.txt","r",stdin);
//**********************************data
const int maxn=500000+5;
const int maxm=500000+5;
int n,m,s;
int f[maxn],head[maxn],cnt=0;
struct node
{
int e;
int next;
}edge[maxn<<1];
struct ans
{
int question;//某一次询问的对象
int id;//这个对象对应的序号
};
vector<ans>vec[maxn];//定义一个二维变长数组的方法
bool vis[maxn];
int output[maxm];
//**********************************function defination
inline int read();//快读
int find(int u);//并查集函数
void tarjan(int u,int fa);//tarjan函数(dfs)
void datasetting();//处理输入数据函数
void addl(int u,int v);//边表加边函数
//**********************************main
int main()
{
fre
datasetting();
tarjan(s,-1);//根的fa是-1
for(int i=0;i<m;i++)printf("%d\n",output[i]);输出ans
return 0;
}
//**********************************function
void addl(int u,int v)
{
edge[cnt].e=v;
edge[cnt].next=head[u];
head[u]=cnt++;
}
void datasetting()
{
memset(head,-1,sizeof(head));
memset(vis,0,sizeof(vis));
n=read();m=read();s=read();
int u,v;
for(int i=0;i<n-1;i++)
{
u=read();v=read();
addl(u,v);
addl(v,u);
}
for(int i=0;i<m;i++)
{//为了让答案有序,我们需要将对应的询问节点与询问次序结合起来,于是就想到用结构体(在这里是ans)
u=read();v=read();
ans r;r.id=i;r.question=v;
vec[u].push_back(r);
r.question=u;
vec[v].push_back(r);
}
}
inline int read()
{
int ans=0;
char r=getchar();
while(r<'0'||r>'9')r=getchar();
while(r>='0'&&r<='9')
{
ans=ans*10+r-'0';
r=getchar();
}
return ans;
}
int find(int u)
{
return f[u]= f[u]==u ? u:find(f[u]);
//问号表达式,若问号左边的条件成立,则返回冒号左边的值,否则返回冒号右边的值
}
void tarjan(int u,int fa)
{
f[u]=u;//先将自己作为根节点
for(int i=head[u];i!=-1;i=edge[i].next)//遍历
{
int v=edge[i].e;
if(v==fa)continue;//这是为什么函数实参里要带一个fa的原因,可以避免从子走到父,保证dfs序
tarjan(v,u);//向下dfs
f[find(v)]=u;//在下面的询问都处理完了以后将下面的点并到自己身上来
}
vis[u]=true;//dfs标记
int s=vec[u].size();
for(int i=0;i<s;i++)//检查每一个关于u的询问
{
int v=vec[u][i].question;int e=vec[u][i].id;
if(vis[v])output[e]=find(v);//若成立,在output数组里面(即答案数组)按顺序放入答案
}
}
/**********************************
ID:Andrew_82
LANG:C++
PROG:LCA
**********************************/
倍增
以上是tarjan算法,接下来我们来看一下倍增算法
对与一棵树里的两个点,我们要找其最近公共祖先,最容易想到的方法就是将俩个点不断的向上追溯其祖先,直到找到第一个相交的祖先即可
就像这样
以下所有图片来自
http://www.cnblogs.com/yyf0309/p/5972701.html)
但是,一个一个的跳,也太慢了,我们可不可以大步大步的跳?
答案是可以的
设树的最大深度为d,对于每一个节点,最坏情况下与根节点的距离为就是d
然后,我们可以将d拆成不多于20个2的n次方的数相加的形式
每次跳 2 i {2}^{i} 2i步( 0 < i < = ln d ln 2 0<i<=\frac{\ln d}{\ln2} 0<i<=ln2lnd,i倒序遍历)
这样就可以避免缓慢的跳跃了
这也是倍增算法的原理
当然,要实现这一功能,我们需要一个表 f [ i ] [ j ] f[i][j] f[i][j],表示i号节点向上跳跃 2 j {2}^{j} 2j步所到达的点
这个表可以用以下递推式来求得:
f [ i ] [ j ] = f [ f [ i ] [ j − 1 ] ] [ j − 1 ] f[i][j]=f[f[i][j-1]][j-1] f[i][j]=f[f[i][j−1]][j−1]
即i向上
2
j
{2}^{j}
2j,等价于i向上
2
j
−
1
{2}^{j-1}
2j−1步后继续向上
2
j
−
1
{2}^{j-1}
2j−1步
其实这不难理解,毕竟:
2
j
−
1
+
2
j
−
1
=
2
1
×
2
j
−
1
=
2
j
−
1
+
1
=
2
j
{2}^{j-1}+{2}^{j-1}={2}^{1} \times{2}^{j-1}={2}^{j-1+1}={2}^{j}
2j−1+2j−1=21×2j−1=2j−1+1=2j
不难看出,f[i][j−1]是i的上层节点,于是递推顺序应该是从上往下(dfs就满足这个特性),当上层的点的f值被算出来后,就可以放心的计算本层的f值了
下面给出代码(luoguP3379)
#include<bits/stdc++.h>
using namespace std;
int n,m,s,maxd=0,b;
const int maxn=500000+5,maxm=500000+5;
int dfn[maxn];//其实这里是指深度而非时间戳
int f[maxn][20];//倍增数组
struct node{
int e;
int next;
}edge[maxm<<1];
int head[maxn],cnt=0;
inline int read()//快读
{
int ans=0;
char r;r=getchar();
while(r<'0'||r>'9')r=getchar();
while(r>='0'&&r<='9')
{
ans=ans*10+r-'0';
r=getchar();
}
return ans;
}
void addl(int u,int v)//加边不说了
{
edge[cnt].e=v;
edge[cnt].next=head[u];
head[u]=cnt++;
}
void datasetting()
{
memset(head,-1,sizeof(head));
memset(f,-1,sizeof(f));
n=read();m=read();s=read();
int u,v;
for(int i=0;i<n-1;i++)//based on one
{
u=read();v=read();
addl(u,v);
addl(v,u);
}
}
void dfs(int u)//主要目的是要求得最大深度和f[i][0](即每个点的father值),为后面的倍增数组做准备
{
if(f[u][0]!=-1)maxd=max(maxd,dfn[u]=dfn[f[u][0]]+1);//同时完成给子节点赋值和求最大深度两件事
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].e;
if(v==f[u][0])continue;
f[v][0]=u;
dfs(v);
}
}
void Double()//求倍增数组,i的第2^j个父亲 是i的第2^(j-1)个父亲的第2^(j-1)个父亲。
{
for(int j=1;j<=b;j++)
{
for(int i=1;i<=n;i++)
{
if(f[i][j-1]!=-1)f[i][j]=f[f[i][j-1]][j-1];
}
}
}
int LCA(int u,int v)//先让u,v处于同一高度,然后在逐次向上以倍增的方式提,直到出答案
{
if(dfn[u]<dfn[v]){int weret=u;u=v;v=weret;}
for(int i=b;i>=0;i--)if(f[u][i]!=-1&&dfn[f[u][i]]>=dfn[v])u=f[u][i];
if(v==u)return v;
for(int i=b;i>=0;i--)
{
if(f[u][i]!=f[v][i])
{
u=f[u][i];
v=f[v][i];
}
}
return f[v][0];
}
int main()
{
freopen("datain.txt","r",stdin);
datasetting();
dfs(s);
b=(int)(log(maxd)/log(2));//这是在对这棵树中最深的一个点的深度取2的对数
//这个点向上走2^b层不会超过根节点的深度,而走2^(b+1)步则会
Double();//其实最好不要用这个名字,因为容易忽略大写D
int s,e;
for(int i=0;i<m;i++)
{
s=read();e=read();
printf("%d\n",LCA(s,e));
}
return 0;
}
/*************************************************
ID: Andrew_82
LANG: C++
PROG: LCA
*************************************************/
loj#10130:
#include<bits/stdc++.h>
using namespace std;
#define loop(i,start,end) for(register int i=start;i<=end;++i)
#define clean(arry,num); memset(arry,num,sizeof(arry));
#define max(a,b) ((a>b)?a:b)
#define min(a,b) ((a<b)?a:b)
int n,q,cnt=0;
const int maxn=1e5+10;
struct node
{
int e;
int nxt;
}edge[maxn<<1];
int head[maxn];
int jump[maxn][30];
int dep[maxn];
inline int read()
{
int _ans=0;bool _neg=false;char _r=getchar();
while(_r>'9'||_r<'0'){if(_r=='-')_neg=true;_r=getchar();}
while(_r>='0'&&_r<='9'){_ans=_ans*10+_r-'0';_r=getchar();}
return (_neg)?-_ans:_ans;
}
inline void addl(int u,int v)
{
edge[cnt].e=v;
edge[cnt].nxt=head[u];
head[u]=cnt;
cnt++;
}
void dfs(int u,int fa)
{
jump[u][0]=fa;
for(int i=1;(1<<i)<=dep[u];++i)//在dfs模块里打表,代码量减少
//用(1<<i)<=dep[u]来代替了求树的最大深度,更好理解和操作
if(jump[u][i-1]!=-1)
jump[u][i]=jump[jump[u][i-1]][i-1];
for(int i=head[u];i!=-1;i=edge[i].nxt)
{
int v=edge[i].e;
if(v==fa)continue;
dep[v]=dep[u]+1;
dfs(v,u);
}
}
int lca(int x,int y)
{
if(x==y)return x;//不要忘了这一句
if(dep[x]<dep[y]){int t=x;x=y;y=t;}
for(int i=20;i>=0;--i)
{
if(dep[jump[x][i]]>=dep[y])x=jump[x][i];
}
if(x==y)return x;//注意这一句也不能忘
for(int i=20;i>=0;--i)
{
if(jump[x][i]!=jump[y][i])x=jump[x][i],y=jump[y][i];
}//跳到LCA的下面
return jump[x][0];
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("datain.txt","r",stdin);
#endif
n=read();
clean(head,-1);
clean(jump,-1);
clean(dep,0);
loop(i,1,n-1)
{
int x,y;
x=read(),y=read();
addl(x,y);
addl(y,x);
}
dfs(1,-1);
q=read();
loop(i,1,q)
{
int x,y;
x=read();
y=read();
int LCA=lca(x,y);
printf("%d\n",dep[x]+dep[y]-(dep[LCA]<<1));
}
return 0;
}