倍增lca学习笔记

倍增lca学习笔记

前置

倍增

例题P3865 【模板】ST表

#include <cstdio>
#include <algorithm>
#include <cstring>
#define maxn 500010

using namespace std;

int n, m, st[maxn][21], llog2[maxn];

void init()
{
    for(int j = 1; (1 << j) <= n; j++)
        for(int i = 1; i + (1 << j) - 1 <= n; i++)
            st[i][j] = max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
    for(int i = 2; i <= n; i++)
    {
        llog2[i] = llog2[i - 1];
        if((1 << (llog2[i] + 1)) == i) llog2[i]++;
    }
}

int query(int l, int r)
{
    int k = llog2[r - l + 1];
    return max(st[l][k], st[r - (1 << k) + 1][k]);
}

int main() {
    int l, r;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) scanf("%d", &st[i][0]);
    init();
    while(m--)
    {
        scanf("%d%d", &l, &r);
        printf("%d\n", query(l, r));
    }
    return 0;
}

st[i][j]表示从 i 开始往后 $ 2 ^ j$个数里的最大值

llog[maxn]是为了快速计算log

init初始化时,用类似于递推的方法预处理好所有\(2 ^ j\)的长度的最大值

并且处理好\(i\)对应的log值(log手动模拟一下就懂)

查询时数据不一定刚好是\(2^j\),所以我们为了要让st的端点刚好覆盖查询点

用log便可以完成这个操作,虽然最后查询时的两段st会有重叠部分,但对答案没有影响

LCA

有tarjan和倍增两种写法(我知道的)

tarjan可以看看同校巨巨写的

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

#include <cstdio>
#include <algorithm>
#include <cstring>
#define maxn 600005

using namespace std;

int n, m, cnt, rt, dp[maxn][21], head[maxn], deep[maxn];

struct Edge{
    int v, next;
}e[maxn << 1];

void add(int u, int v)
{
    e[++cnt].v = v;
    e[cnt].next = head[u];
    head[u] = cnt;
}

void dfs(int x, int fa)
{
    dp[x][0] = fa;
    deep[x] = deep[fa] + 1;
    for(int i = head[x]; i; i = e[i].next)
    {
        int v = e[i].v;
        if(v == fa) continue;
        dfs(v, x);
    }
}

void init()
{
    for(int j = 1; (1 << j) <= n; j++)
        for(int i = 1; i <= n; i++)
            if(deep[i] >= (1 << j)) 
                dp[i][j] = dp[dp[i][j - 1]][j - 1];
}

int lca(int x, int y)
{
    if(deep[x] < deep[y]) swap(x, y);
    int d = deep[x] - deep[y];
    for(int j = 20; j >= 0; j--) if(d & (1 << j)) x = dp[x][j];
    if(x == y) return x;
    for(int j = 20; j >= 0; j--)
    {
        if(dp[x][j] != dp[y][j])
        {
            x = dp[x][j];
            y = dp[y][j];
        }
    }
    return dp[x][0];
}

int main() {
    int u, v;
    scanf("%d%d%d", &n, &m, &rt);
    for(int i = 1; i < n; i++)
    {
        scanf("%d%d", &u, &v);
        add(u, v);
        add(v, u);
    }
    dfs(rt, 0);
    init();
    for(int i = 1; i <= m; i++)
    {
        scanf("%d%d", &u, &v);
        printf("%d\n", lca(u, v));
    }
    return 0;
}

dp[i][j]\(i\)\(2^j\)级祖先

链式前向星建边不说了


void dfs(int x, int fa)
{
    dp[x][0] = fa;
    deep[x] = deep[fa] + 1;
    for(int i = head[x]; i; i = e[i].next)
    {
        int v = e[i].v;
        if(v == fa) continue;
        dfs(v, x);
    }
}

dfs预处理深度

你的深度时你爸爸的深度 + 1


void init()
{
    for(int j = 1; (1 << j) <= n; j++)
        for(int i = 1; i <= n; i++)
            if(deep[i] >= (1 << j)) 
                dp[i][j] = dp[dp[i][j - 1]][j - 1];
}

倍增思想

\(i\)的每\(2^j\)级祖先都预处理出来

\(2^j\) = \(2^{j-1}\) + \(2^{j-1}\)


int lca(int x, int y)
{
    if(deep[x] < deep[y]) swap(x, y);
    int d = deep[x] - deep[y];
    for(int j = 20; j >= 0; j--) if(d & (1 << j)) x = dp[x][j];
    if(x == y) return x;
    for(int j = 20; j >= 0; j--)
    {
        if(dp[x][j] != dp[y][j])
        {
            x = dp[x][j];
            y = dp[y][j];
        }
    }
    return dp[x][0];
}

保持x更深,所以要交换

不断将x向上提(j从大到小防止跳过头)

如果x == y则说明两个点在一条线上,直接输出x

否则继续同时将x和y向上提

当他们的\(2^j\)级祖先相同了跳出循环

输出dp[x][0]


留坑待填8.1

猜你喜欢

转载自www.cnblogs.com/wyswyz/p/11280128.html