最近公共祖先(lca)

最近公共祖先(Lowest Common Ancestor)

倍增

倍增(求 \(k\) 级祖先)

  • 倍增预处理:
for(int i = 1; i < MAXL; i++){ // 求树上 K 级祖先
	for(int j = 1; j <= n; j++){ // n 为节点数量
		anc[i][j] = anc[i - 1][anc[i - 1][j]];
	}
}
  • 查询:有两种,一种是暴力,一种是 lowbit,听到 x & (-x)你一定想到了树状数组
for(int j = MAXL - 1; j >= 0; j--){
	if(k & (1 << j)) x = anc[j][x];
}
for(int i = 1, cnt = 0; i < MAXN; i *= 2, cnt++) Log[i] = cnt;
while(k > 0){
	int u = k & (-k);
	x = anc[Log[u]][x];
	k -= u;
}

LCA思想(求 \(x\)\(y\) 的最近公共祖先)

不利用倍增的LCA

  • 不断上升 \(x\)\(y\)
  • \(x\)\(y\) 深,上升 \(x\)
  • \(y\)\(x\) 深,上升 \(y\)

利用倍增的LCA

  • 先使 \(x\)\(y\) 的深度相同(只需要上升更加深的,可以将 abs(dep[x] - dep[y]) 看做 \(k\) 做倍增)
  • 第一种做法二分 \(x\)\(y\) 同时上升多少在同一点上(mid大于答案,所有mid都满足)
  • 第二种做法如下:
int Lca(int x, int y){ // 求 x 和 y 的最近公共祖先
  if(dep[x] > dep[y]) swap(x, y);
  y = Find(y, dep[y] - dep[x]); // 平衡 x 和 y 的深度
  if(y == x) return x;
  for(int j = MAXL - 1; j >= 0; j--){ // 可以利用第一种做法发现的性质
    if(anc[j][x] != anc[j][y]){
      x = anc[j][x], y = anc[j][y];
    }
  }
  return anc[0][y];
}

树链剖分

  • 将数分成最多 \(\log n\) 条链
点击 luogu LCA 模板题树链剖分代码
#include <bits/stdc++.h>

using namespace std;
using LL = long long;

const int MAXN = 5e5 + 3;

int n, m, root;
int etot = 0, estart[MAXN], eg[MAXN * 2], enxt[MAXN * 2], etop[MAXN]; // 链式前向星
int fa[MAXN], sz[MAXN], dep[MAXN], son[MAXN];                         // 父亲、子树大小、深度、重儿子
int top[MAXN];                                                        // 所在重链的顶点

void ADDeg(int U, int V){
  etot++, estart[U] = (estart[U] == 0 ? etot : estart[U]);
  enxt[etop[U]] = etot, etop[U] = etot;
  eg[etot] = V;
}
void mdfs1(int x, int dad){ // 求出:父亲、子树大小、深度、重儿子
  fa[x] = dad, dep[x] = dep[dad] + 1, sz[x] = 1, son[x] = 0;
  for(int e = estart[x], nxt = eg[e]; e > 0; e = enxt[e], nxt = eg[e]){
    if(nxt == dad) continue;
    mdfs1(nxt, x), sz[x] += sz[nxt];
    if(son[x] == 0 || sz[son[x]] < sz[nxt]) son[x] = nxt;
  }
}
void mdfs2(int x, int ntop){ // 求出:所在重链的顶点
  top[x] = ntop;
  if(son[x] > 0) mdfs2(son[x], ntop);
  for(int e = estart[x], nxt = eg[e]; e > 0; e = enxt[e], nxt = eg[e]){
    if(nxt != fa[x] && nxt != son[x]) mdfs2(nxt, nxt);
  }
}

int main(){
  ios::sync_with_stdio(0), cin.tie(0); 
  cin >> n >> m >> root;
  for(int i = 1, U, V; i < n; i++){
    cin >> U >> V, ADDeg(U, V), ADDeg(V, U);
  }
  mdfs1(root, 0), mdfs2(root, root);
  for(int sb = 1, X, Y; sb <= m; sb++){
    cin >> X >> Y;
    while(top[X] != top[Y]){
      if(dep[top[X]] < dep[top[Y]]) swap(X, Y);
      X = fa[top[X]];
    }
    cout << (dep[X] > dep[Y] ? Y : X) << "\n";
  }
  return 0;
}
点击查看 luogu 树链剖分模板题代码
#include <bits/stdc++.h>

using namespace std;
using LL = long long;

const int MAXN = 1e5 + 3;

struct Segment_Tree{
  LL sum, lz;
}egtr[MAXN * 4];
LL egopt, egret;

int n, m, root;
LL mod, a[MAXN];
int etot = 0, estart[MAXN], eg[MAXN * 2], enxt[MAXN * 2], etop[MAXN]; // 链式前向星,卡常不得不防
int fa[MAXN], sz[MAXN], dep[MAXN], son[MAXN];                         // 父亲、子树大小、深度、重儿子
int top[MAXN], dfntop = 0, dfn[MAXN], mxdfn[MAXN];                    // 所在重链的顶点、dfs序上的编号、子树内的最大编号

inline void ADDeg(int U, int V){
  etot++, estart[U] = (estart[U] == 0 ? etot : estart[U]);
  enxt[etop[U]] = etot, etop[U] = etot;
  eg[etot] = V;
}

void mdfs1(int x, int dad){ // 求出:父亲、子树大小、深度、重儿子
  fa[x] = dad, dep[x] = dep[dad] + 1, sz[x] = 1, son[x] = 0;
  for(int e = estart[x], nxt = eg[e]; e > 0; e = enxt[e], nxt = eg[e]){
    if(nxt == dad) continue;
    mdfs1(nxt, x), sz[x] += sz[nxt];
    if(son[x] == 0 || sz[son[x]] < sz[nxt]) son[x] = nxt;
  }
}
void mdfs2(int x, int ntop){ // 求出:所在重链的顶点、dfs序上的编号
  top[x] = ntop, dfn[x] = ++dfntop;
  if(son[x] > 0) mdfs2(son[x], ntop);
  for(int e = estart[x], nxt = eg[e]; e > 0; e = enxt[e], nxt = eg[e]){
    if(nxt != fa[x] && nxt != son[x]) mdfs2(nxt, nxt);
  }
  mxdfn[x] = dfntop;
}

void Downlz(int i, int l, int r, LL lz){ // 下传懒惰标记
  egtr[i].lz += lz, egtr[i].sum += lz * (r - l + 1);
}
void S(int i, int l, int r, int tl, int tr){ // 区间修改+区间查询  线段树
  if(l == tl && r == tr){
    if(egopt == 1){
      egtr[i].lz += egret, egtr[i].sum += egret * (r - l + 1);
    }else{
      egret += egtr[i].sum;
    }
    return;
  }
  int mid = (l + r) >> 1;
  Downlz(i * 2, l, mid, egtr[i].lz), Downlz(i * 2 + 1, mid + 1, r, egtr[i].lz);
  egtr[i].lz = 0;
  if(tl <= mid){
    S(i * 2, l, mid, tl, min(tr, mid));
  }
  if(mid + 1 <= tr){
    S(i * 2 + 1, mid + 1, r, max(tl, mid + 1), tr);
  }
  egtr[i].sum = egtr[i * 2].sum + egtr[i * 2 + 1].sum;
}

int main(){
  ios::sync_with_stdio(0), cin.tie(0); 
  //freopen("P3384_8.in", "r", stdin);
  //freopen("P3384_.out", "w", stdout);
  cin >> n >> m >> root >> mod;
  for(int i = 1; i <= n; i++) cin >> a[i], a[i] %= mod;
  for(int i = 1, U, V; i < n; i++) cin >> U >> V, ADDeg(U, V), ADDeg(V, U);
  mdfs1(root, 0), mdfs2(root, root);
  for(int i = 1; i <= n; i++){ // 初始化 
    egopt = 1, egret = a[i], S(1, 1, n, dfn[i], dfn[i]); 
  }
  for(int sb = 1, op, X, Y; sb <= m; sb++){
    cin >> op >> X;
    LL ans = 0;
    if(op == 1){
      cin >> Y >> egret, egopt = 1;
      while(top[X] != top[Y]){ // 修改路径
        if(dep[top[X]] < dep[top[Y]]) swap(X, Y);
        S(1, 1, n, dfn[top[X]], dfn[X]);
        X = fa[top[X]];
      }
      if(dep[X] < dep[Y]) swap(X, Y);
      S(1, 1, n, dfn[Y], dfn[X]);
    }else if(op == 2){
      cin >> Y, egopt = 2, egret = 0;
      while(top[X] != top[Y]){ // 查询路径
        if(dep[top[X]] < dep[top[Y]]) swap(X, Y);
        S(1, 1, n, dfn[top[X]], dfn[X]);
        X = fa[top[X]];
      }
      if(dep[X] < dep[Y]) swap(X, Y);
      S(1, 1, n, dfn[Y], dfn[X]);
      cout << egret % mod << "\n"; // 输出 ans
    }else if(op == 3){
      cin >> egret, egopt = 1;
      S(1, 1, n, dfn[X], mxdfn[X]); // 修改子树
    }else if(op == 4){
      egopt = 2, egret = 0, S(1, 1, n, dfn[X], mxdfn[X]);
      cout << egret % mod << "\n";
    }else cout << "1145141919810\n";
  }
  return 0;
}
posted @ 2023-10-17 21:21  hhhqx  阅读(5)  评论(0编辑  收藏  举报