利用倍增求LCA
LCA的定义
LCA就是树上两个点的最近公共祖先,
注意:自己和自己的LCA是自己,
好,进入正题,倍增。
倍增
倍增有点像树状数组。。。
最重要的定义
定义
f[i][j]为结点i的第2j个祖先,父亲结点为第一个祖先即20个祖先。
这个也很好求,
f[i][j]=f[f[i][j-1]][j-1]
边界条件:f[i][0]为i的父亲结点
可以直接初始化出来。
值得注意的是,递推式只与j-1层有关,所以先循环j,内嵌i。
为了速度也可以交换i和j的意义。
j的范围是0~log2(n)。
两个基本的操作
往上走任意步
定义函数getk(x,k),返回结点x第k个祖先的编号。
其实也很好写,把k拆成二进制,然后用f数组往上走。
int getk(int x,int k)
{
for(int i=0;i<LOG;i++)
if(k&(1<<i)) x=f[x][i];
return x;
}
嗯。
LOG是一个常数,比log2(n)稍大一点。(用以开数组)
往上走到任意深度
定义函数getd(x,d),返回x的哪个祖先深度为d。
就更好写了,用深度差就可以确定要走到第几个祖先,然后getk。
注意是往上走,不要往下走走到任意深度。
int getd(int x,int d)
{
return getk(x,depth[x]-d);
}
//depth[x]代表x的深度,求深度一个简单的dfs就OK了
求LCA
方法一:笨办法
两个结点依次往上走走到根,生成两个数列。从两条序列的末尾往前走,走到最后一个相同的结点,就是LCA。
如:
2 3 4 5 6 7
9 8 1 5 6 7
7相同,6相同,5相同,4和1不同,所以5是LCA。
时间复杂度:
dfs最多两个Θ(n),查找还有一个Θ(n)。所以总时间复杂度:Θ(n)
有人问我为什么不能从前往后找到第一个相同的就是LCA。
这样是可以的,但是如果两个序列长度不一样,就很不方便。
所以,为什么不能让序列一样长呢?
于是,自然地,我们要先统一高度。
方法二:二分
我们有高效的getd算法,怎么用?
我们自然就想到枚举二分深度,然后用getd看一看它们的祖先是不是一样的,然后一样就往下,不一样就往上。
嗯,搞定。
这个的时间复杂度为
Θ(log2n)2
和方法一对比一下:
嗯,有趣的函数。
为什么我会说这么多嗯?
嗯,不再说嗯了。
嗯,上代码。
int YiDianErYeBuBaoLiDeErFen(int u,int v)
{
if(dep[u]<dep[v]) t=u,u=v,v=t;
u=getd(u,dep[v]);
if(u==v) return u;
int l=1,r=dep[v];
while(l<=r)
{
int mid=(l+r)>>1;
if(getd(u,mid)==getd(v,mid)) l=mid+1;
else r=mid-1;
}
return f[getd(u,l)][0];
}
方法三:正规倍增方法
也很简单,跟方法二基本思路差不多,可以直接上代码。
int lca(int u,int v)
{
if(dep[u]<dep[v]) t=u,u=v,v=t;
u=getd(u,dep[v]);
if(u==v) return u;
for(int i=LOG-1;i>=0;i--)
if(f[u][i]!=f[v][i])
u=f[u][i],v=f[v][i];
return cut[u];
}
时间复杂度为 Θ(log2n)
嗯,讲完了。
一道裸题
【USACO MAR11银组】聚会地点
时间限制: 1 Sec 内存限制: 64 MB
提交: 97 解决: 98
题目描述
Bessie和Jon每天都要去他们所居住的小镇的某些地方游玩。有趣的是,他们居住的小镇是一个树的结构,也就意味是,小镇的每个地方之间有且仅有一条通路(不是指一条边,而是指一条通路),每个地方都会有且仅有一个父亲地点(除了小镇的城镇中心,它没有祖先)。
小镇共有N个地点(1 <= N <= 1,000),编号1~N。点1是镇的中心。
Bessie和Jon决定每天都要在游玩后见面,他们见面的地点总是在他们游玩的两个地方之间的那条通路中,离城镇中心最近的地方,下面给出他们的旅行日程,你需要帮他们每天的见面地点。
你可以理解为城镇中心就是成为在这个树结构上的根。
地点 它的父亲地点
[1] --------- ----------------
/ | \ 1 ---(城镇中心没有父亲)
/ | \ 2 1
[2] [3] [6] 3 1
/ | \ 4 2
/ | \ 5 8
[4] [8] [9] 6 1
/ \ 7 8
/ \ 8 6
[5] [7] 9 6
以下为他们某次见面的安排:
| Bessie | Jon | Meeting Place |
| --------- | -------- | --------------- |
| 2 | 7 | 1 |
| 4 | 2 | 2 |
| 1 | 1 | 1 |
| 7 | 5 | 8 |
| 9 | 5 | 6 |
输入
第1行:两个数N,M代表一共有N个地方,B和J已经进行了M次见面
第2..N-1行,每行一个数X,代表第i个地点的父亲为X
再接下来M行,每行两个数,分别代表B和J当天准备去游玩的地方
1<=N<=1000,1<=M<=1000
输出
一共M行,每行一个数代表B和J当天见面的地方。
样例输入
9 6
1
1
2
8
1
8
6
6
2 7
4 2
3 3
4 1
7 5
9 5
样例输出
1
2
3
1
8
6
嗯,裸题一个,上代码。
详见代码
数据很小,暴力也可以过。
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
const int MAXN=1024;
const int LOG=12;
void Read(int &p)
{
p=0;
char c=getchar();
while(c<'0'||c>'9') c=getchar();
while(c>='0'&&c<='9')
p=p*10+c-'0',c=getchar();
}
int dep[MAXN],cut[MAXN],f[MAXN][LOG],n,m,u,v,t;
vector <int> down[MAXN];
void dfs(int i)
{
if(i!=1) dep[i]=dep[cut[i]]+1;
for(unsigned int j=0;j<down[i].size();j++)
dfs(down[i][j]);
}
int getk(int x,int k)
{
for(int i=0;i<LOG;i++)
if(k&(1<<i)) x=f[x][i];
return x;
}
int getd(int x,int d)
{
return getk(x,dep[x]-d);
}
int lca(int u,int v)
{
if(dep[u]<dep[v]) t=u,u=v,v=t;
u=getd(u,dep[v]);
if(u==v) return u;
for(int i=LOG-1;i>=0;i--)
if(f[u][i]!=f[v][i])
u=f[u][i],v=f[v][i];
return cut[u];
}
int BAOLI(int u,int v)
{
if(dep[u]<dep[v]) t=u,u=v,v=t;
u=getd(u,dep[v]);
if(u==v) return u;
int l=1,r=dep[v];
while(l<=r)
{
int mid=(l+r)/2;
if(getd(u,mid)==getd(v,mid)) l=mid+1;
else r=mid-1;
}
return cut[getd(u,l)];
}
int main()
{
Read(n); Read(m);
for(int i=2;i<=n;i++)
Read(cut[i]),down[cut[i]].push_back(i);
for(int i=1;i<=n;i++) f[i][0]=cut[i];
for(int j=1;j<LOG;j++)
for(int i=1;i<=n;i++)
f[i][j]=f[f[i][j-1]][j-1];
dfs(1);
for(int i=1;i<=m;i++)
Read(u),Read(v),
printf("%d\n",BAOLI(u,v));
}