倍增lca学习笔记
前置
倍增
#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可以看看同校巨巨写的
#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