[洛谷P3379]模板最近公共祖先(LCA)
Posted Memory of winter
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[洛谷P3379]模板最近公共祖先(LCA)相关的知识,希望对你有一定的参考价值。
题目大意:给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。
题解:有多种做法
倍增:(写于2017-9-13)
bfs函数:处理每个节点的深度(其实dfs更常用,当时我用的是bfs)和父亲(dad[i][0])
init函数:处理每个节点的二的整数幂的父亲,dad[i][j]存的是第i个节点的第$2^{j}$个祖先,dad[i][0]就是它的父亲,没有就是0。
可发现每个节点的第$2^{j}$个祖先是它的第$2^{j-1}$个祖先的$2^{j-1}$个祖先,也就是dad[i][j]=dad[dad[i][j-1]][j-1]($2^{n}$=$2^{n-1}$+$2^{n-1}$)。要特别注意一定要把关于j的循环放在外面,因为是一层一层处理的
LCA函数:求LCA,具体见程序中
C++ Code:
#include<cstdio> #define maxn 500100 struct Edge{ int next,to; }edge[maxn<<1]; int dep[maxn],dad[maxn][20]; int head[maxn],cnt; int q[maxn],v[maxn],t=0,w; int n,m,root; void swap(int &a,int &b){a^=b;b^=a;a^=b;} void add(int a,int b){ edge[++cnt].to=b; edge[cnt].next=head[a]; head[a]=cnt; } void bfs(){ v[q[w=1]=root]=dep[root]=1; while (t<w){ int x=q[++t]; for (int i=head[x];i;i=edge[i].next){ int ne=edge[i].to; if (!v[ne]){ v[q[++w]=ne]=1; dep[ne]=dep[dad[ne][0]=x]+1; } } } } void init(){ for (int j=1;j<20;j++){ for (int i=1;i<=n;i++){ dad[i][j]=dad[dad[i][j-1]][j-1]; } } } int LCA(int x,int y){ if (dep[x]<dep[y])swap(x,y);//保证x的深度大于y int temp6=dep[x]-dep[y];//让x的深度与y相同,可以使用二进制的思想,不过一般一个循环搞定(见下方注释处) for (int i=0;i<20;i++){ if (temp6 & (1<<i))x=dad[x][i]; } /*更常用的方法: for (int i=19;i>=0;i--)if (dep[dad[x][i]]>=dep[y])x=dad[x][i]; */ if (x==y)return x;//如果两个已经相同就返回其中一个 for (int i=19;i>=0;i--){//只要dad[x][i]!=dad[y][i],两个节点一起向上跳(如果相同表示跳多了) if (dad[x][i]!=dad[y][i]){ x=dad[x][i]; y=dad[y][i]; } } return dad[x][0];//因为不相同,所以再向上跳一个 } int main(){ scanf("%d%d%d",&n,&m,&root); for (int i=1;i<n;i++){ int a,b; scanf("%d%d",&a,&b); add(a,b); add(b,a); } bfs(); init(); for (int i=0;i<m;i++){ int a,b; scanf("%d%d",&a,&b); printf("%d\\n",LCA(a,b)); } return 0; }
树链剖分:(写于2017-12-15)
dfs1,dfs2函数:树链剖分,见树链剖分
LCA:求LCA,这一部分很简单,如果两个节点不在同一条重链上,就把其中重链顶部深度深的那个节点向上跳到其所在重链顶部的父亲。直到两个节点在同一条重链上,就返回深度较浅的一个
C++ Code:
#include<cstdio> using namespace std; const int maxn=500100; int n,m,r,idx; int dep[maxn],fa[maxn],top[maxn],dfn[maxn],siz[maxn],son[maxn]; int head[maxn],cnt; struct Edge{ int to,nxt; }e[maxn<<1]; void swap(int &a,int &b){a^=b^=a^=b;} void addE(int a,int b){ e[++cnt]=(Edge){b,head[a]}; head[a]=cnt; } void dfs1(int rt){ siz[rt]=1; for (int i=head[rt];i;i=e[i].nxt){ int ne=e[i].to; if (ne!=fa[rt]){ fa[ne]=rt; dep[ne]=dep[rt]+1; dfs1(ne); if (son[rt]==0||siz[ne]>siz[son[rt]])son[rt]=ne; siz[rt]+=siz[ne]; } } } void dfs2(int rt){ dfn[rt]=++idx; int ne=son[rt]; if (ne)top[ne]=top[rt],dfs2(ne); for (int i=head[rt];i;i=e[i].nxt){ ne=e[i].to; if (ne==son[rt]||ne==fa[rt])continue; top[ne]=ne; dfs2(ne); } } int LCA(int x,int y){ while (top[x]!=top[y]){ if(dep[top[x]]<dep[top[y]])swap(x,y); x=fa[top[x]]; } return dep[x]<dep[y]?x:y; } int main(){ scanf("%d%d%d",&n,&m,&r); for (int i=1;i<n;i++){ int a,b; scanf("%d%d",&a,&b); addE(a,b),addE(b,a); } dep[top[r]=r]=1; dfs1(r); dfs2(r); while (m--){ int a,b; scanf("%d%d",&a,&b); printf("%d\\n",LCA(a,b)); } return 0; }
tarjan:(写于2018-8-5)
把询问离线,一次$dfs$即可,$O(n + m) $(有没有$\\alpha$啊?)
C++ Code:
#include <cstdio> #define maxn 500100 using namespace std; int n, m, root, x, y; int vis[maxn], f[maxn], ans[maxn]; int head[maxn], cnt; struct Edge { int to, nxt; } e[maxn << 1]; int headq[maxn], cntq; struct Query { int oth, nxt, tg; } Q[maxn << 1]; void addE(int a, int b) { e[++cnt] = (Edge) {b, head[a]}; head[a] = cnt; } void add(int a, int b, int c) { Q[++cntq] = (Query) {b, headq[a], c}; headq[a] = cntq; } int find(int x) {return ((x == f[x]) ? x : (f[x] = find(f[x])));} void dfs(int rt) { vis[rt] = true; int v; for (int i = head[rt]; i; i = e[i].nxt) { v = e[i].to; if (!vis[v]) { dfs(v); f[v] = find(rt); } } for (int i = headq[rt]; i; i = Q[i].nxt) ans[Q[i].tg] = find(Q[i].oth); } int main() { scanf("%d%d%d", &n, &m, &root); for (int i = 1; i < n; i++) { f[i] = i; scanf("%d%d", &x, &y); addE(x, y); addE(y, x); } f[n] = n; for (int i = 0; i < m; i++) { scanf("%d%d", &x, &y); add(x, y, i); add(y, x, i); } dfs(root); for (int i = 0; i < m; i++) { printf("%d\\n", ans[i]); } }
RMQ:(写于2018-11-26)
$ST$表维护$RMQ$,先$dfs$,每次经过一个点就把这个点加入一个数组(可以证明,数组长度为$2n-1$),记$pos_i$为第$i$个点第一次在数组中出现的位置。求$x,y$的$LCA$,就是求区间$[pos_x,pos_y]$中深度最浅的点。$ST$表维护即可
卡点:$lg$数组没开两倍
C++ Code:
#include <cstdio> #include <algorithm> #include <cctype> namespace __IO { namespace R { int x, ch; inline int read() { ch = getchar(); while (isspace(ch)) ch = getchar(); for (x = ch & 15, ch = getchar(); isdigit(ch); ch = getchar()) x = x * 10 + (ch & 15); return x; } } } using __IO::R::read; #define maxn 500010 int head[maxn], cnt; struct Edge { int to, nxt; } e[maxn << 1]; inline void addedge(int a, int b) { e[++cnt] = (Edge) {b, head[a]}; head[a] = cnt; e[++cnt] = (Edge) {a, head[b]}; head[b] = cnt; } int n, m, rt; int LG[maxn << 1]; #define M 20 int ST[M][maxn << 1], dep[maxn], pos[maxn], idx; void dfs(int u, int fa = 0) { ST[0][++idx] = u, pos[u] = idx; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (v != fa) { dep[v] = dep[u] + 1; dfs(v, u); ST[0][++idx] = u; } } } inline int getmin(int a, int b) {return dep[a] < dep[b] ? a : b;} inline int LCA(int x, int y) { int l = pos[x], r = pos[y]; if (l > r) std::swap(l, r); int tmp = LG[r - l + 1]; return getmin(ST[tmp][l], ST[tmp][r - (1 << tmp) + 1]); } int main() { n = read(), m = read(), rt = read(); for (int i = 1, a, b; i < n; i++) { a = read(), b = read(); addedge(a, b); } dfs(rt); for (int i = 1; i < 20; i++) { for (int j = 1; j + (1 << i) - 1 <= idx; j++) { ST[i][j] = getmin(ST[i - 1][j], ST[i - 1][j + (1 << i - 1)]); } } LG[0] = -1; for (int i = 1; i <= idx; i++) LG[i] = LG[i >> 1] + 1; while (m --> 0) { int x = read(), y = read(); printf("%d\\n", LCA(x, y)); } return 0; }
以上是关于[洛谷P3379]模板最近公共祖先(LCA)的主要内容,如果未能解决你的问题,请参考以下文章