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]+int((1<
那么,我们可以设 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
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
// cout<
// cout<
// }
for(int i=1; i<=m; i++)
work();
return 0;
}
完美切题 ∼ \sim ∼