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;
};

只要其中的信息足够用于之后的区间合并即可。

合并

设左右两个相邻区间的信息分别为:

\[Info(l,m) \]

和:

\[Info(m+1,r) \]

则整个区间的信息为:

\[Info(l,r)=op(Info(l,m),Info(m+1,r)) \]

表示该如何合并左右两个区间的信息。

以区间和举例:

\[Info(l,m)=\sum_{i=l}^{m}A_i \]

\[Info(m+1,r)=\sum_{i=m+1}^{r}A_i \]

则:

\[Info(l,r)=Info(l,m)+Info(m+1,r) \]

故我们令:

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 必须满足结合律

\[op(op(a,b),c)=op(a,op(b,c)) \]

因为线段树会把一个区间拆成若干段进行合并,合并方式可能不同,但最终得到的信息必须相同。

并不要求满足交换律,即:

\[op(a,b) \]

不一定等于:

\[op(b,a) \]

比如矩阵乘法,字符串拼接等信息同样可以维护。

空区间

e() 表示空区间的信息。

要求:

\[op(e(),Node)=op(Node,e())=Node \]

e() 为被维护运算的幺元

比如区间和:

Node e() {
    return Node(0);
}

对于最大值:

Node e() {
    return Node(-INF);
}

所以从代数上来说,Nodeope 构成一个幺半群

修改信息

F 表示一次单点修改中需要携带的信息。

比如维护单点加:

\[A_p\leftarrow A_p+x \]

我们只需要知道这一次要加多少,故令:

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)

即完成:

\[5\leftarrow5+3 \]

对于单点取最大值:

\[A_p\leftarrow\max(A_p,x) \]

可以令:

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;
}

即可。

posted @ 2026-08-15 16:28  曼波绿豆哈基蜂  阅读(3)  评论(0)    收藏  举报