最近公共祖先
约 251 个字 540 行代码 预计阅读时间 8 分钟
最近公共祖先简称 LCA(Lowest Common Ancestor)。两个节点的最近公共祖先,就是这两个点的公共祖先里面,离根最远的那个。
各种算法使用场合¶
倍增优点:代码短,是可以维护动态加叶子的树;缺点:不能修改
树剖优点:比倍增快,支持修改;缺点:代码长
tarjan处理离线询问最快
题目¶
#include<iostream>
#include<cstdio>
using namespace std;
const int maxn = 5e5 + 5;
struct edge{
int to;
int next;
}edge[maxn << 1];
int head[maxn], cnt;
void add(int x, int y){
edge[++cnt].to = y;
edge[cnt].next = head[x];
head[x] = cnt;
}
int depth[maxn], fa[maxn][21];
int lg[maxn];
void dfs(int now, int fath){
fa[now][0] = fath; // 直接父亲
depth[now] = depth[fath] + 1;
for(int i = 1;i < lg[depth[now]];i++){ // 10011011 fa[now][8] fa[now][9 - 20]
fa[now][i] = fa[fa[now][i-1]][i-1];
}
for(int i = head[now];i;i = edge[i].next){
if(edge[i].to != fath) dfs(edge[i].to, now);
}
}
int lca(int x, int y){
if(depth[x] < depth[y]) swap(x, y);//令dep[x] > dep[y]
while(depth[x] > depth[y]){ // 统一深度
x = fa[x][lg[depth[x] - depth[y]] - 1];// 10011011 00011011 00001011 00000011 00000001 00000000
}
if(x == y) return x; // 如果相遇了就是最近公告祖先
for(int k = lg[depth[x] - 1];k >= 0;k--){ // 先跨大步再跨小步 10011011 7 8
if(fa[x][k] != fa[y][k]){ // !!!
x = fa[x][k];
y = fa[y][k];
}
}
return fa[x][0]; // 最后再跳一步
}
int main(void){
for(int i = 1;i <= 10;i++){
lg[i] = lg[i-1] + (1 << lg[i-1] == i);
}
int n, m, s;
scanf("%d %d %d",&n, &m, &s);
for(int i = 1;i < n;i++){
int x, y;
scanf("%d %d",&x, &y);
add(x, y);
add(y, x);
}
dfs(s,0); // 预处理
for(int i = 1;i <= m;i++){
int x, y;
scanf("%d %d",&x, &y);
printf("%d\n",lca(x, y));
}
return 0;
}
倍增求LCA板子¶
const int N=5e5+3;
const int M=__lg(N)+1;
struct EDGE
{
int to,nxt;
};
struct LCA
{
EDGE e[N<<1];
int fa[N][M],dep[N],head[N],lg[N];
int tot;
queue<int> q;
void init(int n)
{
tot=0;
for(int i=1;i<n;++i)
lg[i]=lg[i-1]+(1<<lg[i-1]==i);
}
void AddEdge(int u,int v)
{
e[++tot]={u,head[v]};
head[v]=tot;
e[++tot]={v,head[u]};
head[u]=tot;
}
void bfs(int root)
{
q.push(root);
dep[root]=1;
int u,v,DEP;
while(!q.empty())
{
u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nxt)
{
v=e[i].to;
if(dep[v])
continue;
q.push(v);
dep[v]=dep[u]+1;
fa[v][0]=u;
DEP=lg[dep[u]];
for(int j=1;j<=DEP;++j)
{
fa[v][j]=fa[fa[v][j-1]][j-1];
}
}
}
}
int lca(int x,int y)
{
if(dep[x]>dep[y])
swap(x,y);
for(int i=0,dif=dep[y]-dep[x];dif;++i,dif>>=1)
{
if(dif&1)
y=fa[y][i];
}
if(x==y)
return x;
for(int i=lg[dep[x]];i>=0;--i)
{
if(fa[x][i]==fa[y][i])
continue;
x=fa[x][i];
y=fa[y][i];
}
return fa[x][0];
}
};
树剖求LCA板子(有点权,有边权,无权)¶
点权¶
//W和revW,原点权,按dfs序的点权
//区别于普通线段树,唯一修改build函数
//默认树以1为根
//dfs1处理重儿子,子树大小,深度,父节点
//dfs2处理重链链顶,点的dfs序
//为了减少和线段树模块的耦合度,把原点权按dfs序重排
//querySum 查询路径u-v上所有点的点权合并信息,具体和线段树有关
//update 修改路径u-v上所有点的点权,具体和线段树有关
const int N = 5e4 + 3;
ll W[N], revW[N];
struct edge
{
int v, nxt;
};
struct SegmentTree
{
struct node
{
ll sum,lz;
}st[N<<2];
void pushup(int id)
{
st[id].sum=(st[id<<1].sum+st[id<<1|1].sum);
}
void pushdown(int id,int lsonlen,int rsonlen)
{
if (!st[id].lz)
return;
st[id<<1].lz+=st[id].lz;
st[id<<1|1].lz+=st[id].lz;
st[id<<1].sum+=st[id].lz*lsonlen;
st[id<<1|1].sum+=st[id].lz*rsonlen;
st[id].lz=0;
}
void build(int id,int l,int r)
{
if (l ==r)
{
st[id]={revW[l],0};
return;
}
int mid=(l + r)/2;
build(id << 1,l,mid);
build(id<<1|1,mid + 1,r);
pushup(id);
}
void update(int id,int segl,int segr,int l,int r,ll val)
{
if (l <=segl && segr <=r)
{
st[id].sum+=val*(segr-segl+1);
st[id].lz+=val;
return;
}
int mid=(segl + segr)/2;
pushdown(id,mid-segl+1,segr-mid);
if (l <=mid)
update(id << 1,segl,mid,l,r,val);
if (r > mid)
update(id<<1|1,mid+1,segr,l,r,val);
pushup(id);
}
ll query(int id,int segl,int segr,int l,int r)
{
if (l <=segl && segr <=r)
return st[id].sum;
int mid=(segl + segr)/2;
pushdown(id,mid-segl+1,segr-mid);
ll res=0;
if (l <=mid)
res+=query(id << 1,segl,mid,l,r);
if (r > mid)
res+=query(id<<1|1,mid+1,segr,l,r);
return res;
}
};
struct heavyPathDecomposition
{
int edgeTot, n, dfsOrder, head[N];
edge e[N << 1];
int son[N], siz[N], dep[N], fa[N];
int top[N], dfn[N];
SegmentTree st;
void init(int nn)
{
n = nn;
memset(head, 0, sizeof(int) * (n + 1));
memset(son, -1, sizeof(int) * (n + 1));
edgeTot = 0;
dfsOrder = 0;
}
void init2()
{
dep[1] = 1;
dfs1(1);
dfs2(1, 1);
for (int i = 1; i <= n; ++i)
revW[dfn[i]] = W[i];
st.build(1,1,n);
}
void addEdge(int u, int v)
{
e[++edgeTot] = {v, head[u]};
head[u] = edgeTot;
}
void dfs1(int u)
{
siz[u] = 1;
int v;
for (int i = head[u]; i; i = e[i].nxt)
{
v = e[i].v;
if (v == fa[u])
continue;
dep[v] = dep[u] + 1;
fa[v] = u;
dfs1(v);
siz[u] += siz[v];
if (son[u] == -1 || siz[v] > siz[son[u]])
son[u] = v;
}
}
void dfs2(int u, int Top)
{
top[u] = Top;
dfn[u] = ++dfsOrder;
if (son[u] == -1)
return;
dfs2(son[u], Top);
for (int i = head[u]; i; i = e[i].nxt)
{
if (e[i].v != son[u] && e[i].v != fa[u])
dfs2(e[i].v, e[i].v);
}
}
ll querySum(int u, int v)
{
ll res = 0;
while (top[u] != top[v])
{
if (dep[top[u]] < dep[top[v]])
swap(u, v);
res += st.query(1,1,n,dfn[top[u]], dfn[u]);
u = fa[top[u]];
}
if (dfn[u] > dfn[v])
swap(u, v);
return res + st.query(1,1,n, dfn[u], dfn[v]);
}
void update(int u, int v, ll x)
{
while (top[u] != top[v])
{
if (dep[top[u]] < dep[top[v]])
swap(u, v);
st.update(1,1,n, dfn[top[u]], dfn[u], x);
u = fa[top[u]];
}
if (dfn[u] > dfn[v])
swap(u, v);
st.update(1,1,n, dfn[u], dfn[v], x);
}
} hp;
hp.init(n);
hp.addEdge(u,v);
hp.init2();
hp.update(u,v,x);
hp.querySum(u,v);
边权¶
//把边权固定到边相连的两个点中,深度更深的那个点,就转化为点权
//dfsOrder初始化为-1,因为1为根时,它没有点权
//match是第i条边所固定的点
//只有n-1条边,所以线段树是n-1
//同一条重链上,两点之间的边不包括lca固定的那条边,所以+1,因此还有可能越界,加个判断
const int N = 1e5 + 3;
ll revW[N];
struct edge
{
int v, nxt, id;
ll w;
};
struct SegmentTree
{
struct node
{
ll sum,lz;
}st[N<<2];
void pushup(int id)
{
st[id].sum=(st[id<<1].sum+st[id<<1|1].sum);
}
void pushdown(int id,int lsonlen,int rsonlen)
{
if (!st[id].lz)
return;
st[id<<1].lz+=st[id].lz;
st[id<<1|1].lz+=st[id].lz;
st[id<<1].sum+=st[id].lz*lsonlen;
st[id<<1|1].sum+=st[id].lz*rsonlen;
st[id].lz=0;
}
void build(int id,int l,int r)
{
if (l ==r)
{
st[id]={revW[l],0};
return;
}
int mid=(l + r)/2;
build(id << 1,l,mid);
build(id<<1|1,mid + 1,r);
pushup(id);
}
void update(int id,int segl,int segr,int l,int r,ll val)
{
if (l <=segl && segr <=r)
{
st[id].sum+=val*(segr-segl+1);
st[id].lz+=val;
return;
}
int mid=(segl + segr)/2;
pushdown(id,mid-segl+1,segr-mid);
if (l <=mid)
update(id << 1,segl,mid,l,r,val);
if (r > mid)
update(id<<1|1,mid+1,segr,l,r,val);
pushup(id);
}
ll query(int id,int segl,int segr,int l,int r)
{
if (l <=segl && segr <=r)
return st[id].sum;
int mid=(segl + segr)/2;
pushdown(id,mid-segl+1,segr-mid);
ll res=0;
if (l <=mid)
res+=query(id << 1,segl,mid,l,r);
if (r > mid)
res+=query(id<<1|1,mid+1,segr,l,r);
return res;
}
};
struct heavyPathDecomposition
{
int edgeTot, n, dfsOrder, head[N];
edge e[N << 1];
int son[N], siz[N], dep[N], fa[N];
int top[N], dfn[N];
int match[N];
ll W[N];
SegmentTree st;
void init(int nn)
{
n = nn;
memset(head, 0, sizeof(int) * (n + 1));
memset(son, -1, sizeof(int) * (n + 1));
edgeTot = 0;
dfsOrder = -1;
}
void init2()
{
dep[1] = 1;
dfs1(1);
dfs2(1, 1);
for (int i = 2; i <= n; ++i)
revW[dfn[i]] = W[i];
st.build(1, 1, n - 1);
}
void addEdge(int u, int v, int id, ll w)
{
++edgeTot;
e[edgeTot].v = v;
e[edgeTot].nxt = head[u];
e[edgeTot].id = id;
e[edgeTot].w = w;
head[u] = edgeTot;
}
void dfs1(int u)
{
siz[u] = 1;
int v;
for (int i = head[u]; i; i = e[i].nxt)
{
v = e[i].v;
if (v == fa[u])
continue;
dep[v] = dep[u] + 1;
fa[v] = u;
match[e[i].id] = v;
W[v] = e[i].w;
dfs1(v);
siz[u] += siz[v];
if (son[u] == -1 || siz[v] > siz[son[u]])
son[u] = v;
}
}
void dfs2(int u, int Top)
{
top[u] = Top;
dfn[u] = ++dfsOrder;
if (son[u] == -1)
return;
dfs2(son[u], Top);
for (int i = head[u]; i; i = e[i].nxt)
{
if (e[i].v != son[u] && e[i].v != fa[u])
dfs2(e[i].v, e[i].v);
}
}
ll querySum(int u, int v)
{
ll res = 0;
while (top[u] != top[v])
{
if (dep[top[u]] < dep[top[v]])
swap(u, v);
res += st.query(1,1,n, dfn[top[u]], dfn[u]);
u = fa[top[u]];
}
if (dfn[u] > dfn[v])
swap(u, v);
if (u != v)
res += st.query(1,1,n, dfn[u] + 1, dfn[v]);
return res;
}
void update(int i, ll x)
{
st.update(1,1,n, dfn[match[i]], x);
}
} hp;
hp.init(n);
hp.addEdge(u,v,i,w);
hp.init2();
hp.update(i,x);
hp.querySum(u,v);
单纯求lca¶
const int N = 5e4 + 3;
struct edge
{
int v, nxt;
};
struct heavyPathDecomposition
{
int edgeTot,head[N];
edge e[N<<1];
int son[N], siz[N], dep[N], fa[N];
int top[N];
void init(int n)
{
memset(head, 0, sizeof(int) * (n + 1));
memset(son, -1, sizeof(int) * (n + 1));
edgeTot = 0;
}
void init2()
{
dep[1] = 1;
dfs1(1);
dfs2(1,1);
}
void addEdge(int u, int v)
{
e[++edgeTot] = {v, head[u]};
head[u] = edgeTot;
}
void dfs1(int u)
{
siz[u] = 1;
int v;
for (int i = head[u]; i; i = e[i].nxt)
{
v = e[i].v;
if (v == fa[u])
continue;
dep[v] = dep[u] + 1;
fa[v] = u;
dfs1(v);
siz[u] += siz[v];
if (son[u] == -1 || siz[v] > siz[son[u]])
son[u] = v;
}
}
void dfs2(int u, int Top)
{
top[u] = Top;
if (son[u] == -1)
return;
dfs2(son[u], Top);
for (int i = head[u]; i; i = e[i].nxt)
{
if (e[i].v != son[u] && e[i].v != fa[u])
dfs2(e[i].v, e[i].v);
}
}
int lca(int u,int v)
{
while(top[u]!=top[v])
{
if(dep[top[u]]<dep[top[v]])
swap(u,v);
u=fa[top[u]];
}
return (dep[u]>dep[v]?v:u);
}
}hp;
hp.init(n);
hp.addEdge(u,v);
hp.init2();
hp.lca(u,v);
一些例题¶
hdu3966 点权,路径加法,单点查询
poj2763 边权,单边修改为x,路径和查询
poj3237 边权,单边修改为x,路径取相反数,路径最大值查询
loj10141 点权,路径覆盖,路径颜色段数量查询
黑暗爆炸1036 点权,单点修改,路径和,路径最大值查询
spoj qtree 边权,单边修改为x,路径最大值查询
loj1348 点权,单点修改,路径和