LCA 最近公共祖先

LCA

一般作为解题的一个工具来使用。

意思就是最近公共祖先,所以是需要指定根的。

算法有tarjan,树剖,倍增,LCT,长链剖分,ST表O(1)lca等...

ST表O(1)lca:可以用四毛子算法优化到O(n)-O(1)(你觉得我会去学吗?)

大概就是,做出欧拉序,然后lca(x,y)就是他们之间深度最小的点。

注意长度是两倍。

 

 1 #include <cstdio>
 2 #include <algorithm>
 3 
 4 const int N = 500010;
 5 
 6 struct Edge {
 7     int nex, v;
 8 }edge[N << 1]; int top;
 9 
10 int e[N], ST[N << 1][20], d[N << 1], pos[N], tot, pw[N << 1];
11 
12 inline void add(int x, int y) {
13     top++;
14     edge[top].v = y;
15     edge[top].nex = e[x];
16     e[x] = top;
17     return;
18 }
19 
20 void DFS(int x, int deep) {
21     ST[++tot][0] = x;
22     d[x] = deep;
23     pos[x] = tot;
24     for(int i = e[x]; i; i = edge[i].nex) {
25         int y = edge[i].v;
26         if(!pos[y]) {
27             DFS(y, deep + 1);
28             ST[++tot][0] = x;
29             //d[tot] = deep;
30         }
31     }
32     return;
33 }
34 
35 inline void prework() {
36     for(int i = 2; i <= tot; i++) {
37         pw[i] = pw[i >> 1] + 1;
38     }
39     for(int j = 1; j <= pw[tot]; j++) {
40         for(int i = 1; i + (1 << j) - 1 <= tot; i++) {
41             if(d[ST[i][j - 1]] <= d[ST[i + (1 << (j - 1))][j - 1]]) {
42                 ST[i][j] = ST[i][j - 1];
43             }
44             else {
45                 ST[i][j] = ST[i + (1 << (j - 1))][j - 1];
46             }
47         }
48     }
49     return;
50 }
51 
52 inline int lca(int x, int y) {
53     x = pos[x];
54     y = pos[y];
55     if(x > y) {
56         std::swap(x, y);
57     }
58     int t = pw[y - x + 1];
59     if(d[ST[x][t]] >= d[ST[y - (1 << t) + 1][t]]) {
60         return ST[y - (1 << t) + 1][t];
61     }
62     return ST[x][t];
63 }
64 
65 int main() {
66     int n, m, x, y, root;
67     scanf("%d%d%d", &n, &m, &root);
68     for(int i = 1; i < n; i++) {
69         scanf("%d%d", &x, &y);
70         add(x, y);
71         add(y, x);
72     }
73     DFS(root, 1);
74     prework();
75 
76     while(m--) {
77         scanf("%d%d", &x, &y);
78         printf("%d\n", lca(x, y));
79     }
80 
81     return 0;
82 }
AC代码

 

倍增:

由于刚学了ST表,再来学习这个就觉得十分的容易。

fa[i][j]表示i节点的第1<<j辈的父亲。

查找时首先把x和y调到同一deep,然后再从大往小跳。

反正就是那个东西,看代码,看代码。(md表示max deep)

 1 #include <cstdio>
 2 #include <algorithm>
 3 using namespace std;
 4 const int N = 500010;
 5 struct Edge
 6 {
 7     int u,v,next;
 8 }edge[N<<1];int top;
 9 int point[N],fa[N][30],deep[N],md,n;
10 inline void add(int x,int y)
11 {
12     top++;
13     edge[top].u=x;
14     edge[top].v=y;
15     edge[top].next=point[x];
16     point[x]=top;
17     return;
18 }
19 void dfs(int op,int d)
20 {
21     deep[op]=d;
22     int ed,i=point[op];
23     while(i)
24     {
25         ed=edge[i].v;
26         if(deep[ed]) fa[op][0]=ed;
27         else dfs(ed,d+1);
28         i=edge[i].next;
29     }
30     return;
31 }
32 void pre_lca()
33 {
34     for(int j=1;j<=md;j++)
35     {
36         for(int i=1;i<=n;i++) fa[i][j]=fa[fa[i][j-1]][j-1];
37     }
38     return;
39 }
40 int lca(int x,int y)
41 {
42     if(deep[x]>deep[y]) swap(x,y);
43     for(int i=md;i>=0;i--)
44     {
45         if(deep[fa[y][i]]>deep[x])  y=fa[y][i];
46         else if(deep[fa[y][i]]==deep[x]){y=fa[y][i];break;}
47     }
48     if(deep[x]!=deep[y]) y=fa[y][0];///this!
49     if(deep[x]!=deep[y]) printf("*************\n");
50     if(x==y) return x;
51     for(int i=md;i>=0;i--)
52     {
53         if(fa[x][i]!=fa[y][i]) {x=fa[x][i];y=fa[y][i];}
54     }
55     return x==y?x:fa[x][0];
56 }
57 int main()
58 {
59     int m,r;
60     scanf("%d%d%d",&n,&m,&r);
61     while((1<<md)<=n) md++;
62     md--;
63     int x,y;
64     for(int i=1;i<n;i++)
65     {
66         scanf("%d%d",&x,&y);
67         add(x,y);
68         add(y,x);
69     }
70     dfs(r,1);
71     pre_lca();
72     while(m--)
73     {
74         scanf("%d%d",&x,&y);
75         printf("%d\n",lca(x,y));
76     }
77 
78     return 0;
79 }
LCA_倍增

咳咳,博客园奇葩排版。

Tarjan算法:

首先读入所有的询问。

然后从根开始DFS,开始DFS的时候把该点标为1,结束时标为2,并在并查集上与其父节点合并。

DFS完点x的所有子节点之后,处理关于x的询问:(这里不能先处理询问后DFS,理由是超时...)

对于询问(x, y), 如果 y.tag != 0 那么 ans = find(y)

最后输出就可以啦!

小小优化:把tag换成bool类型,去掉 tag = 2 的操作。

代码实现中要用到vector

实测跑的速度很依赖O(2),我觉得可能与vector有关。

Tarjan: 3200ms

  O(2): 1700ms

倍增:   2500ms

  O(2): 2000ms

 1 #include <cstdio>
 2 #include <vector>
 3 const int N = 500010;
 4 
 5 int n, root, ufs[N], ans[N];
 6 
 7 struct Edge {
 8     int v, nex;
 9 }edge[N << 1]; int top;
10 
11 struct Poi {
12     int e;
13     bool tag;
14     std::vector<int> ask;
15     std::vector<int> id;
16 }poi[N];
17 
18 inline void add(int x, int y) {
19     top++;
20     edge[top].v = y;
21     edge[top].nex = poi[x].e;
22     poi[x].e = top;
23     return;
24 }
25 
26 inline void add_Q(int x, int y, int i) {
27     poi[x].ask.push_back(y);
28     poi[x].id.push_back(i);
29     return;
30 }
31 
32 int find(int x) {
33     if(ufs[x] == x) {
34         return x;
35     }
36     ufs[x] = find(ufs[x]);
37     return ufs[x];
38 }
39 
40 void LCA_Tarjan(int op) {
41     poi[op].tag = 1;
42     for(int i = poi[op].e; i; i = edge[i].nex) {
43         int ed = edge[i].v;
44         if(poi[ed].tag) {
45             continue;
46         }
47         LCA_Tarjan(ed);
48         ufs[ed] = op;
49     }
50     for(int i = 0; i < poi[op].ask.size(); i++) {
51         int y = poi[op].ask[i];
52         if(poi[y].tag) {
53             ans[poi[op].id[i]] = find(y);
54         }
55     }
56     return;
57 }
58 
59 int main() {
60     int m;
61     scanf("%d%d%d", &n, &m, &root);
62     for(int i = 1, x, y; i < n; i++) {
63         scanf("%d%d", &x, &y);
64         add(x, y);
65         add(y, x);
66     }
67     for(int i = 1, x, y; i <= m; i++) {
68         scanf("%d%d", &x, &y);
69         add_Q(x, y, i);
70         add_Q(y, x, i);
71     }
72 
73     for(int i = 1; i <= n; i++) {
74         ufs[i] = i;
75     }
76 
77     LCA_Tarjan(root);
78 
79     for(int i = 1; i <= m; i++) {
80         printf("%d\n", ans[i]);
81     }
82 
83     return 0;
84 }
LCA_Tarjan
posted @ 2018-04-04 13:00  huyufeifei  阅读(230)  评论(0编辑  收藏  举报
试着放一个广告栏(虽然没有一分钱广告费)

『Flyable Heart 応援中!』 HHG 高苗京铃 闪十PSS 双六 電動伝奇堂 章鱼罐头制作组 はきか 祝姬 星降夜