Luogu3379 模板最近公共祖先(LCA)

Posted cj-xxz

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Luogu3379 模板最近公共祖先(LCA)相关的知识,希望对你有一定的参考价值。

题面

题解

这里讲一种硬核做法。

首先\(\mathrmdfs\)整棵树,求出这棵树的欧拉序,然后\(\mathrmLCA\)问题就变成了\(\pm 1\mathrmRMQ\)问题。

考虑\(\mathrmO(n)\)解决\(\pm 1\mathrmRMQ\)问题。

将原序列分块,每一块长度为\(\dfrac \log_2 n2\),块外用\(\mathrmST\)表预处理,复杂度\(\mathrmO(n)\),考虑块内如何\(\mathrmO(1)\)回答。

因为相邻两项之差最多为\(1\),所以块内本质不同的状态只有\(2 ^ \frac \log n 2 = \sqrt n\)种。

那么可以设\(f[S][l][r]\)表示状态为\(S\)时,区间\([l, r]\)的最小值。

于是块内就能\(\mathrmO(1)\)解决了,这一部分预处理的复杂度为\(\mathrmO(\sqrt n \log^2n)\)

因为以上操作复杂度均没有超过\(\mathrmO(n)\),所以预处理的复杂度为\(\mathrmO(n)\),总复杂度为\(\mathrmO(n) - \mathrmO(1)\)

代码

#include <cstdio>
#include <cmath>
#include <algorithm>
#define file(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)

inline int read()

    int data = 0, w = 1; char ch = getchar();
    while (ch != '-' && (ch < '0' || ch > '9')) ch = getchar();
    if (ch == '-') w = -1, ch = getchar();
    while (ch >= '0' && ch <= '9') data = data * 10 + (ch ^ 48), ch = getchar();
    return data * w;


const int maxn(500010), LEN(1050), BLK(10), N(200010);
struct edge  int next, to;  e[maxn << 1];
int head[maxn], e_num, n, m, S, dep[maxn], f[1 << BLK][BLK][BLK];
int A[maxn << 1], ST[BLK << 1][N], Log[maxn << 1], pos[maxn], cnt;
int Len, Blk, minv[N], set[N], B[maxn << 1];
inline int min(const int &x, const int &y)  return B[x] < B[y] ? x : y; 
inline int Pos(const int &x)  return (x - 1) / Len + 1; 
inline int Posy(const int &x)  return (x - 1) % Len; 
inline void add_edge(int from, int to)

    e[++e_num] = (edge) head[from], to;
    head[from] = e_num;


void dfs(int x, int fa)

    A[pos[x] = ++cnt] = x, B[cnt] = dep[x];
    for (int i = head[x]; i; i = e[i].next)
    
        int to = e[i].to; if (to == fa) continue;
        dep[to] = dep[x] + 1, dfs(to, x), A[++cnt] = x, B[cnt] = dep[x];
    


void Init()

    Len = std::max(1, (int) (log(cnt * 1.) / log(2.) * .5));
    Blk = cnt / Len + (cnt % Len > 0); int SIZ = 1 << (Len - 1);
    for (int i = 2; i <= cnt; i++) Log[i] = Log[i >> 1] + 1;
    for (int i = 0; i < SIZ; i++) for (int l = 0; l < Len; l++)
        for (int r = (f[i][l][l] = l) + 1, now = 0, _min = 0; r < Len; r++)
        
            f[i][l][r] = f[i][l][r - 1];
            if (i & (1 << (r - 1))) ++now;
            else  --now; if(now < _min) _min = now, f[i][l][r] = r; 
        
    for (int i = 1; i <= cnt; i++)
        if (!Posy(i)) minv[Pos(i)] = i, set[Pos(i)] = 0;
        else
        
            if (B[i] < B[minv[Pos(i)]]) minv[Pos(i)] = i;
            if (B[i] > B[i - 1]) set[Pos(i)] |= 1 << (Posy(i) - 1);
        
    for (int i = 1; i <= Blk; i++) ST[0][i] = minv[i];
    for (int i = 1; i <= Log[Blk]; i++)
        for (int j = 1; j <= Blk - (1 << i) + 1; j++)
            ST[i][j] = min(ST[i - 1][j], ST[i - 1][j + (1 << (i - 1))]);


int Query(int l, int r)

    l = pos[l], r = pos[r]; if(l > r) std::swap(l, r);
    int idl = Pos(l), idr = Pos(r);
    if (idl == idr) return (idl - 1) * Len + f[set[idl]][Posy(l)][Posy(r)] + 1;
    else
    
        int a1 = (idl - 1) * Len + f[set[idl]][Posy(l)][Len - 1] + 1;
        int a2 = (idr - 1) * Len + f[set[idr]][0][Posy(r)] + 1;
        int ans = min(a1, a2), _l = Log[idr - idl - 1];
        if (idr - idl - 1)
            return min(ans, min(ST[_l][idl + 1], ST[_l][idr - (1 << _l)]));
        return ans;
    


int main()

    n = read(), m = read(), S = read();
    for (int i = 1, a, b; i < n; i++)
        a = read(), b = read(), add_edge(a, b), add_edge(b, a);
    dep[S] = 1, dfs(S, 0); Init();
    for (int a, b; m--; ) a = read(), b = read(), printf("%d\n", A[Query(a, b)]);
    return 0;

以上是关于Luogu3379 模板最近公共祖先(LCA)的主要内容,如果未能解决你的问题,请参考以下文章

Luogu P3379 模板最近公共祖先(LCA)

Luogu P3379 模板最近公共祖先(LCA),树链剖分求LCA模板

luogu3379 模板最近公共祖先(LCA) Tarjan

P3379 模板最近公共祖先(LCA)(倍增LCA)

洛谷——P3379 模板最近公共祖先(LCA)

洛谷 P3379 模板最近公共祖先(LCA) 题解