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 }
倍增:
由于刚学了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 }
咳咳,博客园奇葩排版。
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 }