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
4 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
这道题有两种方法:暴力*and倍增*。
先来看看倍增吧。。
建一个二维数组f
,f[i][j]
表示节点i的第 2j 个祖先。
可知i的枚举范围为 O(n) ,j的枚举范围为 O(logn)
可得出:
即
for(int i=1;i<=n;i++) f[i][0]=fa[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];
定位节点x的第k个祖先(父亲为第一个祖先):
根据倍增数组的性质,一次只能向上跳2的次幂个节点。
将k拆成二进制数,即可通过2的次幂凑出k。
觉得有点像线段树。。。
int getk(int x,int k){
for(int i=0;iif(k&(1<return x;
}
定位节点x的深度为d的祖先:
int getd(int x,int d){
return getk(x,dep[x]-d);
}
在一棵有根树上找到两个点u,v,它们的共有祖先中离跟越远(深度越大)的节点就叫做最近公共祖先(LCA)。
举个栗子,如上图中,8和9的最近公共祖先是5,3和7的最近公共祖先是1。通常,两个一样的点的最近公共祖先是它自己,所以3和3的最近公共祖先是3。(这不是废话吗。。。)
首先用getd操作,统一两个节点的深度,这样子并不会影响答案。
当两个节点深度相同时,我们既可以通过二分深度+getd操作判定来确定LCA(时间复杂度 O(log2n) ),也可以直接用倍增数组查找LCA。
首先 u=v ,答案就是u。(如果你不特判,很坑的。。)
如果 u≠v ,就可以用下列代码:
for(int i=LOG-1;i>=0;i--)
if(f[u][i]!=f[v][i]){
u=f[u][i];
v=f[v][i];
}
其实它就是从下往上找,找到最后的两个不一样的节点。然后这些点的父亲就是答案了。
附:
二分LCA代码:
int LCA(int u,int v){
if(dep[u]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];
}
数组LCA代码:
int LCA(int u,int v){
if(dep[u]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 f[u][0];
}
#include
#include
using namespace std;
const int N=1000;
const int LOG=20;
int n,m,dep[N+1],fa[N+1],f[N+1][LOG+1];
vector<int> son[N+1];
int read(){
char ch=getchar();int x=0;
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x;
}
int getk(int x,int k){
for(int i=0;iif(k&(1<return x;
}
int getd(int x,int d){
return getk(x,dep[x]-d);
}
void dfs(int x,int deep){
dep[x]=deep;
int len=son[x].size();
for(int i=0;i1);
}
int LCA(int u,int v){
if(dep[u]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 f[u][0];
}
int main()
{
n=read();m=read();
for(int i=2;i<=n;i++){
int x=read();
f[i][0]=x;
son[x].push_back(i);
}
for(int j=1;j<=LOG;j++)
for(int i=2;i<=n;i++)
f[i][j]=f[f[i][j-1]][j-1];
dfs(1,1);
while(m--)
printf("%d\n",LCA(read(),read()));
}
暴力就很简单了,以下图为例。
我们让u,v两个点走到根节点,并把他们的路径记录下来。这里以7和9为例。
7 6 5 1
9 5 1
从路径中可以看出5是他们第一个共同的祖先,所以答案就是5啦。。。暴力出奇迹
代码就不打啦。。。