原题链接
P3379
题目类型:
普及
/
提高
−
{\color{yellow}{普及/提高-}}
普及/提高−
AC记录:Accepted
题目大意
如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。
输入格式
第一行包含三个正整数 N , M , S N,M,S N,M,S,分别表示树的结点个数、询问的个数和树根结点的序号。
接下来 N − 1 N-1 N−1行每行包含两个正整数 x , y x,y x,y,表示 x x x结点和 y y y结点之间有一条直接连接的边(数据保证可以构成树)。
接下来 M M M行每行包含两个正整数 a , b a,b a,b,表示询问 a a a结点和 b b b结点的最近公共祖先。
输出格式
输出包含
M
M
M行,每行包含一个正整数,依次为每一个询问的结果。
S
a
m
p
l
e
\mathbf{Sample}
Sample
I
n
p
u
t
\mathbf{Input}
Input
5 5 4
3 1
2 4
5 1
1 4
2 4
3 2
3 5
1 2
4 5
S a m p l e \mathbf{Sample} Sample O u t p u t \mathbf{Output} Output
4
4
1
4
4
H
i
n
t
&
E
x
p
l
a
i
n
\mathbf{Hint\&Explain}
Hint&Explain
在样例中的图如下。
数据范围
对于
30
%
30\%
30%的数据,
N
≤
10
,
M
≤
10
N≤10,M≤10
N≤10,M≤10。
对于
70
%
70\%
70%的数据,
N
≤
10000
,
M
≤
10000
N≤10000,M≤10000
N≤10000,M≤10000。
对于
100
%
100\%
100%的数据,
N
≤
500000
,
M
≤
500000
N≤500000,M≤500000
N≤500000,M≤500000。
解题思路
本题我们将要用到倍增算法。
如果是Native(暴力)的
L
C
T
LCT
LCT,就是两个节点直接往上跳,跳到同一个节点就可以了。但是,很不幸,洛谷新加了两个点,专门是用来卡直接跳的。(我这两个点都是用倍增吸氧和ios::sync_with_stdio(false)
和cin.tie(0)
才跑了
1.82
s
1.82s
1.82s)
那么,用倍增怎么做呢?
我们都知道,平常我们的倍增,都是从小到大跳
2
k
2^k
2k,但是,这里我们要从大到小。原因也很简单,比如说
10
=
8
+
2
10=8+2
10=8+2,如果从小到大,会发现
1
1
1和
4
4
4不行,还需要回来继续,但是从大到小直接
10
=
8
+
2
10=8+2
10=8+2就行了。
这里,我们可以提前求出一个数组
l
g
lg
lg,其中
l
g
i
=
log
2
i
lg_i=\log_2i
lgi=log2i,可以用lg_2[i]=lg_2[i>>1]
Θ
(
n
)
\Theta(n)
Θ(n)求出,这里就给你们自行理解了。
那么,我们可以设
f
i
,
j
f_{i,j}
fi,j为节点
i
i
i的第
2
i
2^i
2i个父节点。如上图,则
f
5
,
0
=
1
f_{5,0}=1
f5,0=1,这可以帮助我们从当前节点快速跳到前面的节点。
但是,该如何算这个
f
f
f数组呢?
我们可以用一轮
d
f
s
dfs
dfs,求出每一个节点的深度,也可以顺便求出他的
f
f
f数组。这里,我们知道,
2
i
=
2
i
−
1
+
2
i
−
1
2^i=2^{i-1}+2^{i-1}
2i=2i−1+2i−1,所以,一个节点的第
2
i
2^i
2i个父节点就是他的 第
2
i
−
1
2^{i-1}
2i−1个父节点 的 第
2
i
−
1
2^{i-1}
2i−1个父节点。
接下来,就是重头戏 倍增
L
C
A
LCA
LCA 了。
还是和Native算法一样,先让深度深的节点走到和深度浅的节点一样的深度,这里就可以用我在上面说的从大到小的方法遍历。此时,需要特判一下,因为有可能
x
=
y
x=y
x=y,此时
x
x
x就是原来
x
x
x和
y
y
y的
L
C
A
LCA
LCA。如果不是,就按照我上面说的从大到小的顺序同时跳节点,由于我们跳的是两个节点的公共祖先的下面一个位置,所以最后答案为
f
x
,
0
f_{x,0}
fx,0。
上代码
#include<bits/stdc++.h>
using namespace std;
vector<int> road[500010];
bool vis[500010];
int gr_fa[500010][30];
int lg_2[500010];
int dep[500010];
int n,m,root;
void dfs(int pos,int fat)
{
vis[pos]=true;
dep[pos]=dep[fat]+1;
gr_fa[pos][0]=fat;
for(int i=1; i<=lg_2[dep[pos]]-1; i++)
gr_fa[pos][i]=gr_fa[gr_fa[pos][i-1]][i-1];
for(int i=0; i<road[pos].size(); i++)
if(!vis[road[pos][i]])
dfs(road[pos][i],pos);
return;
}
void work()
{
int x,y;
cin>>x>>y;
if(dep[x]<dep[y])
swap(x,y);
while(dep[x]>dep[y])
x=gr_fa[x][lg_2[dep[x]-dep[y]]-1];
if(x==y)
{
cout<<x<<endl;
return;
}
for(int i=lg_2[dep[x]]-1; i>=0; i--)
if(gr_fa[x][i]!=gr_fa[y][i])
x=gr_fa[x][i],y=gr_fa[y][i];
cout<<gr_fa[x][0]<<endl;
return;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n>>m>>root;
for(int i=1; i<n; i++)
{
int x,y;
cin>>x>>y;
road[x].push_back(y);
road[y].push_back(x);
}
for(int i=1; i<=n; i++)
lg_2[i]=lg_2[i-1]+int((1<<lg_2[i-1])==i);
dfs(root,0);
// for(int i=1; i<=n; i++)
// {
// for(int j=0; j<lg_2[dep[i]]; j++)
// cout<<gr_fa[i][j]<<" ";
// cout<<endl;
// }
for(int i=1; i<=m; i++)
work();
return 0;
}
完美切题 ∼ \sim ∼