P14302 基础倍增练习题 6 - 斜二进制倍增
参考:斜二进制倍增。
Update:10.31 更改了之前表述不严谨的地方。
斜二进制倍增
斜二进制倍增,应用在树上问题,可以做到:
- 在线
O(1) 加叶子; - 单次
O(\log n) 查询树链信息,信息不需要可差分(但需要满足结合律); -
- 空间复杂度是小常数
O(n) 。
原理
像树状数组一样,每个点记录包含自己的一段前缀信息。同时每个点也开一个数组记录自己的信息。
设
- 如果
l-1 管辖的区间与x-1 长度相同,那么x 就管辖l-1 管辖的、x-1 管辖的,以及自己的信息; - 这里称
x 管辖的区间是x-1 管辖的、l-1 管辖的区间的上一级区间; - 否则
x 就只管辖自己的信息。
声明一些数组:
实现
单点修改:往
区间查询:往
后缀加点:实现定义即可。
- 如果
l-1 管辖的区间与x-1 长度相同,那么x 就管辖l-1 管辖的、x-1 管辖的,以及自己的信息;- 否则
x 就只管辖自己的信息。
时间复杂度
这里主要讲区间查询的复杂度。
考虑第一次满足
在时刻
在时刻
两部分时间复杂度都是
树上问题
可以很好迁移。具体的,用每个点的深度(根结点深度为
发现某两条树链的信息若有交,那么交的这一部分信息可以共用。
实现时将区间问题的
树上 LCA
两个点中,深度大的点先跳到和深度小的点同级的位置,然后再两个点同时向上跳跃。
树链信息
深度较浅的点跳到 LCA 的深度,深度较深的点跳到 LCA 深度加
题解
题目:P14302 基础倍增练习题 6。
一开始
发现树链剖分不能处理在线连边,ST 表式倍增内存会爆。所以可以用斜二进制倍增。
下面默认
每次连边时,考虑将
每次查询,两边分别往上跳,跳到 LCA 的位置,并把沿途信息记录下来。这一部分也是
所以总体是
代码
最大子段和部分:
struct Max_Seq {
LL vl, vr, va, sum;
Max_Seq(LL vl, LL vr, LL va, LL sum):
vl(vl), vr(vr), va(va), sum(sum) {}
Max_Seq() {}
friend Max_Seq operator + (const Max_Seq & a,const Max_Seq & b) {
return Max_Seq(
max(a.vl, a.sum + b.vl),
max(b.vr, b.sum + a.vr),
max({a.va,b.va,a.vr + b.vl}),
a.sum + b.sum
);
}
}tr[N], val[N];
Max_Seq get_rev(Max_Seq x) {
return Max_Seq(x.vr, x.vl, x.va, x.sum);
}
斜二进制的部分:
int fa[N], dth[N], tf[N], lb[N];
void push_up(int x) {
int p = fa[x];
if (p == lb[x]) {
tr[x] = val[x];
} else {
tr[x] = (val[x] + tr[p]) + tr[lb[p]];
}
}
void append(int cu, int pa) {
int p = pa, q = lb[p], r = lb[q];
if (q == 0 || dth[p] - dth[q] != dth[q] - dth[r]) {
lb[cu] = pa;
} else {
tf[p] = tf[q] = cu;
lb[cu] = r;
}
push_up(cu);
}
int get_lca(int x, int y) {
if (dth[x] > dth[y]) {
swap(x, y);
}
while (dth[y] > dth[x]) {
int p = lb[y];
if (dth[p] <= dth[x] - 1) {
y = fa[y];
} else {
y = p;
}
}
while (x != y) {
if (lb[x] != lb[y]) {
x = lb[x], y = lb[y];
} else {
x = fa[x], y = fa[y];
}
}
return x;
}
查询部分:
Max_Seq get_seq(int cu, int d) {
Max_Seq ans = Max_Seq(-inf, 0, -inf, 0);
while (dth[cu] >= d) {
int p = lb[cu];
if (dth[p] < d - 1) {
ans = ans + val[cu];
cu = fa[cu];
} else {
ans = ans + tr[cu];
cu = p;
}
}
return ans;
}
LL query(int x, int y) {
int lca = get_lca(x, y);
if (x == y) return val[x].va;
if (dth[x] > dth[y]) swap(x, y);
Max_Seq p1 = get_seq(x, dth[lca]);
Max_Seq p2 = get_seq(y, dth[lca] + 1);
return (p1 + get_rev(p2)).va;
}
连边部分:(其中 B 是并查集结构体)
void dfs(int cu, int pa) {
dth[cu] = dth[pa] + 1;
fa[cu] = pa; //注意先更新再 append
append(cu, pa);
for (int to: e[cu]) {
if (to == pa) continue;
dfs(to, cu);
}
}
void unite(int x, int y) {
int tx = B.bcj(x), ty = B.bcj(y);
if (B.sz[tx] < B.sz[ty]) swap(tx, ty), swap(x, y);
dfs(y, x);
e[x].pb(y);
e[y].pb(x);
B.merge(x, y);
}