P3379 【模板】最近公共祖先(LCA)
题目链接:https://www.luogu.com.cn/problem/P3379
方法一:暴力求公共祖先
1.向上标记法(重点了解26-44行)
1 #include<bits/stdc++.h> 2 using namespace std; 3 int n, m, s; 4 const int maxn=5e6; 5 const int maxm=5e6; 6 struct edge{ 7 int t, nex; 8 }e[maxm<<1]; 9 int head[maxn], cnt; 10 void add(int u, int v){ 11 e[++cnt].t=v; 12 e[cnt].nex=head[u]; 13 head[u]=cnt; 14 } 15 int depth[maxn], fa[maxn]; 16 void dfs(int now, int fath) 17 { 18 fa[now]=fath; //fa[i]代表i的父亲 19 depth[now]=depth[fath]+1; 20 //cout<<now<<":"<<fa[now]<<" "<<depth[now]<<endl; 21 for(int i=head[now]; i; i=e[i].nex) 22 if(e[i].t != fath) 23 dfs(e[i].t, now); 24 } 25 26 int flag[maxn]; //向上标记法 27 int lca1(int x, int y) 28 { 29 memset(flag, 0, sizeof(flag));//每次初始化为0 30 if(x==y)return x; 31 flag[x]=1; 32 while(fa[x]){//x向上走到根,根的父亲节点为0 33 x=fa[x]; 34 flag[x]=1; 35 } 36 if(flag[y])//y节点是x的祖先 37 return y; 38 while(fa[y]){ 39 y=fa[y]; 40 if(flag[y]) 41 return y; 42 } 43 return 0;//森林,无LCA 44 } 45 int main() 46 { 47 scanf("%d%d%d", &n, &m, &s); 48 for(int i=1; i<n; i++){ 49 int x, y; 50 scanf("%d%d", &x, &y); 51 add(x, y); add(y, x); 52 } 53 dfs(s, 0); 54 while(m--){ 55 int a, b; 56 scanf("%d%d", &a, &b); 57 printf("%d\n", lca1(a, b)); 58 } 59 return 0; 60 }
2.同步前进法(重点了解25-38行)
1 #include<bits/stdc++.h> 2 using namespace std; 3 int n, m, s; 4 const int maxn=5e6; 5 const int maxm=5e6; 6 struct edge{ 7 int t, nex; 8 }e[maxm<<1]; 9 int head[maxn], cnt; 10 void add(int u, int v){ 11 e[++cnt].t=v; 12 e[cnt].nex=head[u]; 13 head[u]=cnt; 14 } 15 int depth[maxn], fa[maxn]; 16 void dfs(int now, int fath) 17 { 18 fa[now]=fath; //fa[i]代表i的父亲 19 depth[now]=depth[fath]+1;// 20 //cout<<now<<":"<<fa[now]<<" "<<depth[now]<<endl; 21 for(int i=head[now]; i; i=e[i].nex) 22 if(e[i].t != fath) 23 dfs(e[i].t, now); 24 } 25 int lca(int x, int y) //同步前进法 26 { 27 if(x==y)return x;//同点 28 if(fa[x]==fa[y])return fa[x];//同根 29 if(depth[x]<depth[y])//不妨设x的深度 >= y的深度 30 swap(x, y); 31 while(depth[x] > depth[y]) 32 x=fa[x]; 33 if(x==y)return x;//y节点是x的祖先 34 while(fa[x] != fa[y]){//父亲不相等,两个点同时网上爬,直到父亲相等 35 x=fa[x]; y=fa[y]; 36 } 37 return fa[x];//返回父亲节点 38 } 39 int main() 40 { 41 scanf("%d%d%d", &n, &m, &s); 42 for(int i=1; i<n; i++){ 43 int x, y; 44 scanf("%d%d", &x, &y); 45 add(x, y); add(y, x); 46 } 47 dfs(s, 0); 48 while(m--){ 49 int a, b; 50 scanf("%d%d", &a, &b); 51 printf("%d\n", lca(a, b)); 52 } 53 return 0; 54 }
同样是暴力,数据差别还是很大的。
方法二:倍增,ST表
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int maxn=5e5+10; 4 const int maxm=5e5+10; 5 int n, m, s; 6 struct edge{ 7 int to, next; 8 }e[maxm<<1]; 9 int head[maxn],cnt; 10 void add(int u, int v){ 11 e[++cnt].to=v; 12 e[cnt].next=head[u]; 13 head[u]=cnt; 14 } 15 int lg[maxn]; 16 void log_2(){//功能相当于log2函数 17 lg[0]=-1; 18 for(int i=1; i<=n; i++)lg[i]=lg[i/2]+1; 19 } 20 21 int dep[maxn], f[maxn][22];//通过dfs计算各个节点的深度,初始化f[i][0] 22 void dfs(int u) 23 { 24 for(int i=head[u]; i; i=e[i].next){ 25 int v=e[i].to; 26 if(f[u][0] == v)//避免双向重复dfs 27 continue; 28 dep[v]=dep[u]+1;//深度计算 29 f[v][0]=u;//初始化 30 dfs(v); 31 } 32 } 33 void st_create(){//创建st表 34 for(int j=1; j<=lg[n]; j++) 35 for(int i=1; i<=n; i++) 36 f[i][j]=f[f[i][j-1]][j-1]; 37 } 38 39 int lca(int x, int y) 40 { 41 if(dep[x] < dep[y])//不妨设x的深度 >= y的深度 42 swap(x, y); 43 while(dep[x] > dep[y]) 44 x = f[x][lg[dep[x]-dep[y]]]; 45 if(x == y) //如果x是y的祖先,那他们的LCA肯定就是x了 46 return x; 47 for(int k=lg[n]; k>=0; --k) 48 if(f[x][k] != f[y][k])//因为我们要跳到它们LCA的下面一层,所以它们肯定不相等,如果不相等就跳过去。 49 x=f[x][k], y=f[y][k]; 50 return f[x][0];//返回父亲节点 51 } 52 int main() 53 { 54 scanf("%d%d%d",&n, &m, &s); 55 for(int i=1; i<n; i++){ 56 int x, y; 57 scanf("%d%d",&x, &y); 58 add(x, y);add(y, x); 59 } 60 log_2(); 61 dfs(s); 62 st_create(); 63 while(m--){ 64 int a, b; 65 scanf("%d%d",&a, &b); 66 printf("%d\n", lca(a, b)); 67 } 68 return 0; 69 }