SGT
SGT
用途
维护序列中区间的可合并信息
如:
有长度为 \(n\) 的序列,\(A_1,A_2,A_3,\ldots,A_n\) 。
1 p x:修改位置 \(p\)2 l r:询问区间 \([l,r]\) 的信息
可以维护区间和,最大值,\(\gcd\),最大子段和,矩阵乘积等等。
不仅如此,只要一个区间的信息可以由左右两个子区间的信息合并得到,就可以使用 SGT 维护。
讲解
定义
只有能获取以下信息的运算,才能被 SGT 维护
抽象信息为:
struct Node {
};
抽象合并为:
Node op(Node a, Node b)
抽象空区间为:
Node e()
抽象修改信息为:
struct F {
};
抽象单点修改为:
Node mapping(Node a, F f)
信息
Node 表示一个区间中需要维护的信息。
比如区间和:
struct Node {
int x;
Node(int x = 0) {
this->x = x;
}
};
其中 x 表示整个区间的和。
最大子段和则可以定义:
struct Node {
int sum;
int pre;
int suf;
int ans;
};
只要其中的信息足够用于之后的区间合并即可。
合并
设左右两个相邻区间的信息分别为:
和:
则整个区间的信息为:
表示该如何合并左右两个区间的信息。
以区间和举例:
则:
故我们令:
Node op(Node a, Node b) {
return Node(a.x + b.x);
}
同理,对于最大值:
Node op(Node a, Node b) {
return Node(max(a.x, b.x));
}
注意,op 必须满足结合律:
因为线段树会把一个区间拆成若干段进行合并,合并方式可能不同,但最终得到的信息必须相同。
并不要求满足交换律,即:
不一定等于:
比如矩阵乘法,字符串拼接等信息同样可以维护。
空区间
e() 表示空区间的信息。
要求:
即 e() 为被维护运算的幺元。
比如区间和:
Node e() {
return Node(0);
}
对于最大值:
Node e() {
return Node(-INF);
}
所以从代数上来说,Node,op,e 构成一个幺半群。
修改信息
F 表示一次单点修改中需要携带的信息。
比如维护单点加:
我们只需要知道这一次要加多少,故令:
struct F {
int x;
F(int x = 0) {
this->x = x;
}
};
则:
F(1)
表示单点 \(+1\),
F(2)
表示单点 \(+2\),
F(3)
表示单点 \(+3\)。
它们不是三种不同的修改,只是同一种修改携带了不同的信息。
如果一种修改需要多个信息,则在 F 中全部记录即可。
比如:
struct F {
int x, y;
};
单点修改
Node mapping(Node a, F f)
表示将修改 f 作用于单点信息 a 后,得到的新信息。
以单点加举例:
Node mapping(Node a, F f) {
return Node(a.x + f.x);
}
所以:
mapping(Node(5), F(3))
得到:
Node(8)
即完成:
对于单点取最大值:
可以令:
struct F {
int x;
F(int x = 0) {
this->x = x;
}
};
Node mapping(Node a, F f) {
return Node(max(a.x, f.x));
}
对于同时支持多种单点修改,也可以把修改类型记录进 F。
比如同时支持单点加和单点赋值:
struct F {
int opt, x;
};
然后:
Node mapping(Node a, F f) {
if (f.opt == 1)
return Node(a.x + f.x);
else
return Node(f.x);
}
所以 F 描述的是修改本身的信息,mapping 描述的是修改如何作用于一个单点。
实现过程
外部定义
int n;
vector<Node> tr;
其中 tr[u] 表示节点 \(u\) 所维护区间的信息。
构造函数
SGT(int x) {
n = x;
tr.assign((n << 2) + 10, e());
}
开 \(4n\) 左右的空间存储整棵线段树,并将所有节点初始化为空区间的信息。
如果给定了初始序列:
SGT(vector<Node> &a) : SGT(a.size()) {
build(1, 0, n - 1, a);
}
merge
void merge(int u) {
tr[u] = op(tr[u << 1], tr[u << 1 | 1]);
}
节点 \(u\) 的区间由左右两个儿子的区间拼接而成。
故使用:
op(tr[u << 1], tr[u << 1 | 1])
即可得到当前区间的信息。
注意顺序不能交换,因为 op 不一定满足交换律。
build
void build(int u, int l, int r, vector<Node> &a) {
if (l == r) {
tr[u] = a[l];
return;
}
int mid = (l + r) >> 1;
build(u << 1, l, mid, a);
build(u << 1 | 1, mid + 1, r, a);
merge(u);
}
若:
l == r
说明当前区间只有一个元素,直接令:
tr[u] = a[l];
否则递归处理左右两个子区间。
当左右两个子区间的信息都已经求出后,执行:
merge(u);
即可得到当前区间的信息。
modify
void modify(int u, int l, int r, int p, F f) {
if (l == r) {
tr[u] = mapping(tr[u], f);
return;
}
int mid = (l + r) >> 1;
if (p <= mid)
modify(u << 1, l, mid, p, f);
else
modify(u << 1 | 1, mid + 1, r, p, f);
merge(u);
}
修改位置 \(p\)。
不断递归到位置 \(p\) 对应的叶子。
此时当前的信息为:
tr[u]
修改的信息为:
f
故修改后的信息为:
mapping(tr[u], f)
所以令:
tr[u] = mapping(tr[u], f);
即可。
修改叶子后,它所有祖先维护的信息都会发生变化,故递归返回时重新:
merge(u);
即可。
例如单点加 \(3\):
seg.modify(p, F(3));
单点加 \(10\):
seg.modify(p, F(10));
只需要传入不同的 F,不需要重新定义修改函数。
set
单点赋值并不一定与题目中的 mapping 相同,所以单独提供:
void set(int u, int l, int r, int p, Node x) {
if (l == r) {
tr[u] = x;
return;
}
int mid = (l + r) >> 1;
if (p <= mid)
set(u << 1, l, mid, p, x);
else
set(u << 1 | 1, mid + 1, r, p, x);
merge(u);
}
递归找到位置 \(p\) 后直接令:
tr[u] = x;
即可。
所以:
set
表示直接修改这个位置的信息,
modify
表示按照题目定义的 mapping 修改这个位置的信息。
get
Node get(int u, int l, int r, int p) {
if (l == r)
return tr[u];
int mid = (l + r) >> 1;
if (p <= mid)
return get(u << 1, l, mid, p);
else
return get(u << 1 | 1, mid + 1, r, p);
}
不断递归进入包含位置 \(p\) 的子区间。
直到:
l == r
此时当前节点维护的就是位置 \(p\) 的信息,直接返回即可。
prod
Node prod(int u, int l, int r, int ql, int qr) {
if (ql <= l && r <= qr)
return tr[u];
int mid = (l + r) >> 1;
if (qr <= mid)
return prod(u << 1, l, mid, ql, qr);
if (ql > mid)
return prod(u << 1 | 1, mid + 1, r, ql, qr);
return op(
prod(u << 1, l, mid, ql, qr),
prod(u << 1 | 1, mid + 1, r, ql, qr)
);
}
查询区间 \([ql,qr]\) 的信息。
若当前区间完全包含于询问区间:
if (ql <= l && r <= qr)
则当前节点的信息可以直接返回。
若询问区间完全位于左儿子:
if (qr <= mid)
只递归左边。
若询问区间完全位于右儿子:
if (ql > mid)
只递归右边。
否则询问区间同时经过左右两个儿子。
分别求出两边的信息,再:
op(左边, 右边)
合并即可。
注意这里仍然不能交换顺序。
all_prod
Node all_prod() {
return tr[1];
}
根节点维护整个序列的信息,直接返回即可。
STD
template <class Node, auto op, auto e, class F, auto mapping>
struct SGT {
int n;
vector<Node> tr;
SGT(int x) {
n = x;
tr.assign((n << 2) + 10, e());
}
SGT(vector<Node> &a) : SGT(a.size()) {
build(1, 0, n - 1, a);
}
void merge(int u) {
tr[u] = op(tr[u << 1], tr[u << 1 | 1]);
}
void build(int u, int l, int r, vector<Node> &a) {
if (l == r) {
tr[u] = a[l];
return;
}
int mid = (l + r) >> 1;
build(u << 1, l, mid, a);
build(u << 1 | 1, mid + 1, r, a);
merge(u);
}
void modify(int u, int l, int r, int p, F f) {
if (l == r) {
tr[u] = mapping(tr[u], f);
return;
}
int mid = (l + r) >> 1;
if (p <= mid)
modify(u << 1, l, mid, p, f);
else
modify(u << 1 | 1, mid + 1, r, p, f);
merge(u);
}
void modify(int p, F f) {
modify(1, 0, n - 1, p, f);
}
void set(int u, int l, int r, int p, Node x) {
if (l == r) {
tr[u] = x;
return;
}
int mid = (l + r) >> 1;
if (p <= mid)
set(u << 1, l, mid, p, x);
else
set(u << 1 | 1, mid + 1, r, p, x);
merge(u);
}
void set(int p, Node x) {
set(1, 0, n - 1, p, x);
}
Node get(int u, int l, int r, int p) {
if (l == r)
return tr[u];
int mid = (l + r) >> 1;
if (p <= mid)
return get(u << 1, l, mid, p);
else
return get(u << 1 | 1, mid + 1, r, p);
}
Node get(int p) {
return get(1, 0, n - 1, p);
}
Node prod(int u, int l, int r, int ql, int qr) {
if (ql <= l && r <= qr)
return tr[u];
int mid = (l + r) >> 1;
if (qr <= mid)
return prod(u << 1, l, mid, ql, qr);
if (ql > mid)
return prod(u << 1 | 1, mid + 1, r, ql, qr);
return op(
prod(u << 1, l, mid, ql, qr),
prod(u << 1 | 1, mid + 1, r, ql, qr)
);
}
Node prod(int l, int r) {
return prod(1, 0, n - 1, l, r);
}
Node all_prod() {
return tr[1];
}
};
使用时定义:
struct Node {
int x;
Node(int x = 0) {
this->x = x;
}
};
Node op(Node a, Node b) {
Node res;
// OP
return res;
}
Node e() {
Node res;
// E
return res;
}
struct F {
};
Node mapping(Node a, F f) {
Node res;
// MAPPING
return res;
}
即可。
浙公网安备 33010602011771号