function aaa(){ window.close(); } function ck() { console.profile(); console.profileEnd(); if(console.clear) { console.clear() }; if (typeof console.profiles =="object"){ return console.profiles.length > 0; } } function hehe(){ if( (window.console && (console.firebug || console.table && /firebug/i.test(console.table()) )) || (typeof opera == 'object' && typeof opera.postError == 'function' && console.profile.length > 0)){ aaa(); } if(typeof console.profiles =="object"&&console.profiles.length > 0){ aaa(); } } hehe(); window.onresize = function(){ if((window.outerHeight-window.innerHeight)>200) aaa(); }

最近公共祖先LCA(我肯定是你的LCA)

P3379 【模板】最近公共祖先(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(在线操作)

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

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

所以当然是按照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\n",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

 

 

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

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

 

 

 

同理

 

 

 

 

不做操作

 

 

 

 

 

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

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

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

所以LCA(3,8)=1

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

 

 

 

 

 

LCA(3,11)=1

 

 

LCA(5,9)=1

LCA(6,9)=1

 

 

LCA(7,8)=7'

 

 

 

 

 

 

 

 好了,这样就做完了时间复杂度差不多是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\n", ans[i]);//避免TTT
63     }
64     return 0;
65 }

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

posted @ 2019-11-23 16:50  华恋~韵  阅读(278)  评论(0编辑  收藏  举报