最近公共祖先LCA(我肯定是你的LCA) P3379 【模板】最近公共祖先(LCA) 暴力LCA  倍增LCA(在线操作)  Tarjan求LCA(离线操作)

例子

最近公共祖先LCA(我肯定是你的LCA)
P3379 【模板】最近公共祖先(LCA)
暴力LCA
 倍增LCA(在线操作)
 Tarjan求LCA(离线操作)

LCA(13,14)=12

LCA(13,7)=3

LCA(7,11)=2

LCA(11,2)=2

好了,了解以后我们就来康康怎么求吧

暴力LCA

如果找LCA(6,8),我们就要先让它们的深度相等。此时(deep[1]=0)deep[6]=4,deep[8]=3,我们就先让节点6爬到节点5

然后就是愉快的一起爬

让它们一层一层地向上爬,知道爬到同一节点

但是都知道,一旦这棵树有点高,like this,就会TTT

 最近公共祖先LCA(我肯定是你的LCA)
P3379 【模板】最近公共祖先(LCA)
暴力LCA
 倍增LCA(在线操作)
 Tarjan求LCA(离线操作)

因为这样爬着太慢了(青蛙爬树都知道跳着爬)

 倍增LCA(在线操作)

所以我们升级了,会跳着爬了

但是怎么升呢,大家都知道,计算机和二进制都离不开关系

所以当然是按照2的次方来跳,不过我们不是从小往大(1,2,4,8,16,32,64...),而是从大到小(...64,,32,16,8,4,2,1)(因为从小到大加着会超过,到时候很麻烦)

所以我们跳的时候就还要计算一下你这么跳会到哪个节点去(不然你乱跳啊)

总所周知,1+1=2(这不是废话吗)

 1 void dfs(int x,int fa)
 2 {
 3     deep[x]=deep[fa]+1;
 4     f[x][0]=fa;
 5     for(int i=1;(1<<i)<=deep[x];i++)
 6     {
 7         f[x][i]=f[f[x][i-1]][i-1];
 8     }
 9     for(int i=0;i<mp[x].size();i++)
10     {
11         if(mp[x][i]==fa)continue;
12         dfs(mp[x][i],x);
13     }
14 }


我们用f[i][j]表示第i个节点向上跳2^j高度后的祖先节点,我们就不用像个憨批一样去搜了(不然你和暴力有什么区别),他就等于2^(j-1)*2也就是向上跳两次2^(j-1)

所以f[i][j]=f[f[i][]j-1][j-1]

所以我们循坏j从小到大,这样就可以用小的来递推大的了

然后搜索的时候顺便把深度也计算了

预处理完了,我们就要开始干正事了

 1 int LCA(int x,int y)
 2 {
 3     if(deep[x]<deep[y])swap(x,y);
 4     for(int i=log2(deep[x]);i>=0;i--)
 5         if(deep[x]-(1<<i)>=deep[y])
 6             x=f[x][i];
 7     if(x==y)return x;
 8     for(int i=log2(deep[x]);i>=0;i--)
 9     {
10         if(f[x][i]!=f[y][i])
11         {
12             x=f[x][i];
13             y=f[y][i];
14         }
15     }
16     return f[x][0];
17 }

我们先让两个节点到达同一高度(为了方便计算,我们把高的放前面)

然后向上爬

先判断是否在同一点(因为存在两者的祖先是其中一个节点的可能,就是你和你爸的最近公共祖先是你爸)

然后一起向上跳跳跳(jump!!!)

为了避免两者计算出的公共祖先不是最近的(例如你爷爷也是你和你爸的公共祖先)

我们只跳到公共祖先的下一层,也就是第十排的判断

最后再输出它们的最近公共祖先就好了

 1 #include<bits/stdc++.h>
 2 #define N 500005
 3 using namespace std;
 4 int n,m,root;
 5 int f[N][21];
 6 int deep[N];
 7 vector<int> mp[N];
 8 inline int read(){
 9     int x=0,f=1;
10     char ch=getchar();
11     while(ch<'0'||ch>'9'){
12         if(ch=='-')
13             f=-1;
14         ch=getchar();
15     }
16     while(ch>='0'&&ch<='9'){
17         x=(x<<1)+(x<<3)+(ch^48);
18         ch=getchar();
19     }
20     return x*f;
21 }
22 void dfs(int x,int fa)
23 {
24     deep[x]=deep[fa]+1;
25     f[x][0]=fa;
26     for(int i=1;(1<<i)<=deep[x];i++)
27     {
28         f[x][i]=f[f[x][i-1]][i-1];
29     }
30     for(int i=0;i<mp[x].size();i++)
31     {
32         if(mp[x][i]==fa)continue;
33         dfs(mp[x][i],x);
34     }
35 }
36 int LCA(int x,int y)
37 {
38     if(deep[x]<deep[y])swap(x,y);
39     for(int i=log2(deep[x]);i>=0;i--)
40         if(deep[x]-(1<<i)>=deep[y])
41             x=f[x][i];
42     if(x==y)return x;
43     for(int i=log2(deep[x]);i>=0;i--)
44     {
45         if(f[x][i]!=f[y][i])
46         {
47             x=f[x][i];
48             y=f[y][i];
49         }
50     }
51     return f[x][0];
52 }
53 int main()
54 {
55     deep[0]=-1;
56     n=read(),m=read(),root=read();
57     for(int i=1;i<n;i++)
58     {
59         int a,b;
60         a=read(),b=read();
61         mp[a].push_back(b);
62         mp[b].push_back(a);
63     }
64     dfs(root,0);
65     while(m--)
66     {
67         int a,b;
68         a=read(),b=read();
69         printf("%d
",LCA(a,b));
70     }
71     return 0;
72 }

 Tarjan求LCA(离线操作)

  首先,我们来理解一下离线操作。就是输入完问题后一次性解决。而在线操作就是每输入一个问题就可以立即得出答案。其实你硬要把离线做成在线也行(但是每次都要初始化,很麻烦。离线之所以是离线,说明做的方法有共通之处,有些一次就可以解决)

  首先我们需要的数组如下

  

flag []表示是否已经回溯(被搜索过)
fa[]寻找父亲节点
ans[]记录每个问题的答案
e[]链式前向星存边
q[]记录问题的vector

注意,这里的问题要存两个,具体原因我后面会讲到

 我们用的是DFS(深度优先搜索),因为公共祖先是父亲节点嘛,这样的话保证一个点之前的都是自己的祖先(能懂吧,毕竟只是一棵树)

看看问题

3 8
3 11
6 9
5 9
7 8

最近公共祖先LCA(我肯定是你的LCA)
P3379 【模板】最近公共祖先(LCA)
暴力LCA
 倍增LCA(在线操作)
 Tarjan求LCA(离线操作)

因为8,11都未被搜索,所以不做操作,但是回溯的时候要更新fa[]。因为初始值都是自己,回溯才连接父亲节点

此时可以用并查集,方便路径压缩 当然你要暴力也没意见

 最近公共祖先LCA(我肯定是你的LCA)
P3379 【模板】最近公共祖先(LCA)
暴力LCA
 倍增LCA(在线操作)
 Tarjan求LCA(离线操作)

同理

最近公共祖先LCA(我肯定是你的LCA)
P3379 【模板】最近公共祖先(LCA)
暴力LCA
 倍增LCA(在线操作)
 Tarjan求LCA(离线操作)

 最近公共祖先LCA(我肯定是你的LCA)
P3379 【模板】最近公共祖先(LCA)
暴力LCA
 倍增LCA(在线操作)
 Tarjan求LCA(离线操作)

不做操作

 最近公共祖先LCA(我肯定是你的LCA)
P3379 【模板】最近公共祖先(LCA)
暴力LCA
 倍增LCA(在线操作)
 Tarjan求LCA(离线操作)

最近公共祖先LCA(我肯定是你的LCA)
P3379 【模板】最近公共祖先(LCA)
暴力LCA
 倍增LCA(在线操作)
 Tarjan求LCA(离线操作)

关键的来了,众所周知,最近公共祖先就是两个点的祖先节点所构成的链的最先相交的地方,此时蓝色的都是8的父亲,所以我们只需要搜索另外一个点的父亲,而这个操作就可以考虑并查集,当然不用也行。(其实都差不多)一层一层向上搜索,直到一个fa[]等于自己的节点。

可以看出,fa[]等于自己的点要不就是正在搜索没有回溯(也就是当前节点的祖先),要不就是没有被搜索。

怎么保证第一个搜索的一定是蓝色的呢。我们可以假设第一个fa[]等于自己的是没有被搜索的,但是如果没有被搜索的话这个点也不会被搜索,说明这个点的fa[]也等于自己,这是矛盾的,所以不成立。

所以LCA(3,8)=1

但是这里并不能计算 7,8.因为此时7并没有被打上标记(黄色)

 最近公共祖先LCA(我肯定是你的LCA)
P3379 【模板】最近公共祖先(LCA)
暴力LCA
 倍增LCA(在线操作)
 Tarjan求LCA(离线操作)

 最近公共祖先LCA(我肯定是你的LCA)
P3379 【模板】最近公共祖先(LCA)
暴力LCA
 倍增LCA(在线操作)
 Tarjan求LCA(离线操作)

LCA(3,11)=1

最近公共祖先LCA(我肯定是你的LCA)
P3379 【模板】最近公共祖先(LCA)
暴力LCA
 倍增LCA(在线操作)
 Tarjan求LCA(离线操作)

LCA(5,9)=1

LCA(6,9)=1

最近公共祖先LCA(我肯定是你的LCA)
P3379 【模板】最近公共祖先(LCA)
暴力LCA
 倍增LCA(在线操作)
 Tarjan求LCA(离线操作)

LCA(7,8)=7'

最近公共祖先LCA(我肯定是你的LCA)
P3379 【模板】最近公共祖先(LCA)
暴力LCA
 倍增LCA(在线操作)
 Tarjan求LCA(离线操作)

最近公共祖先LCA(我肯定是你的LCA)
P3379 【模板】最近公共祖先(LCA)
暴力LCA
 倍增LCA(在线操作)
 Tarjan求LCA(离线操作)

 最近公共祖先LCA(我肯定是你的LCA)
P3379 【模板】最近公共祖先(LCA)
暴力LCA
 倍增LCA(在线操作)
 Tarjan求LCA(离线操作)

 好了,这样就做完了时间复杂度差不多是O(n+q)

康康代码

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int N=500010;
 4 const int M=500010;
 5 int n,m,s,cnt;
 6 int last[N];//前向星
 7 int fa[N];//记录父亲
 8 int ans[M];//记录答案
 9 bool flag[N];//判断是否被搜索
10 struct node{//记录边的
11     int to,pre;
12 }e[2*M];
13 struct question{//还要记录编号,方便保存并且输出答案
14     int to,num;
15 };
16 vector<question>q[N];//记录问题
17 void add(int x,int y)
18 {
19     cnt++;
20     e[cnt].to=y;
21     e[cnt].pre=last[x];
22     last[x]=cnt;
23 }
24 int get(int x)//找父亲节点
25 {
26     return fa[x]==x?x:(fa[x]=get(fa[x]));
27 }
28 void dfs(int u,int root)//最核心部分
29 {
30     for(int i=last[u];i;i=e[i].pre)
31     {
32         if(e[i].to==root)continue;
33         dfs(e[i].to,u);
34         fa[e[i].to]=u;
35     }
36     for(int i=0;i<q[u].size();i++)
37         if(flag[q[u][i].to])
38             ans[q[u][i].num]=get(q[u][i].to);
39     flag[u]=1;
40 }
41 int main()
42 {
43     scanf("%d%d%d",&n,&m,&s);
44     for(int i=1;i<=n;i++)fa[i]=i;
45     for(int i=1;i<n;i++)
46     {
47         int u,v;
48         scanf("%d%d",&u,&v);
49         add(u,v);
50         add(v,u);
51     }
52     for(int i=1;i<=m;i++)
53     {
54         int x,y;
55         scanf("%d%d",&x,&y);
56         q[x].push_back((question){y,i});//一定要存两次,因为你不知道那个先被搜索到
57         q[y].push_back((question){x,i});
58     }
59     dfs(s,0);
60     for(int i=1;i<=m;i++)
61     {
62         printf("%d
", ans[i]);//避免TTT
63     }
64     return 0;
65 }

好了,就是这样了。喜欢的话点赞支持一下哦