保留历史版本的线段树,每次修改只会修改logn个节点。新版本只需新开logn个节点,其他节点不变。使用动态开点线段树。

// 单点修改,区间求和
#define N 200005
#define lc(x) tr[x].l
#define rc(x) tr[x].r
struct node {
    int l, r;
    ll s;
} tr[N * 25];
// n(logn+3)?

int root[N], idx;

// 初始版本构建build(root[0], l, r)
void build(int& x, int l, int r) {
    x = ++idx;
    if (l == r)
        return;
    int m = l + r >> 1;
    build(lc(x), l, m);
    build(rc(x), m + 1, r);
}

// 单点插入,拷贝前一版本的一条路径
void insert(int x, int& y, int l, int r, int pos, ll val) {
    y = ++idx;
    tr[y] = tr[x];
    if (l == r) {
        tr[y].s += val;
        return;
    }
    int m = l + r >> 1;
    if (pos <= m)
        insert(lc(x), lc(y), l, m, pos, val);
    else
        insert(rc(x), rc(y), m + 1, r, pos, val);
    tr[y].s = tr[lc(y)].s + tr[rc(y)].s;
}

// 查询前缀区间[... pos]的和
ll presum(int x, int l, int r, int pos) {
    if (l == r)
        return tr[x].s;
    int m = l + r >> 1;
    if (m < pos) {
        return tr[lc(x)].s + presum(rc(x), m+1, r, pos);
    } else {
        return presum(lc(x), l, m, pos);
    }
}

// 查询区间和
ll query(int x, int l, int r, int ql, int qr) {
    if (ql <= l && r <= qr)
        return tr[x].s;
    int m = l + r >> 1;
    // 判断查询区间[ql, qr] 覆盖[l,m], [m+1,r]的情况。
    ll rt = 0;
    if (ql<=m) {
        rt += query(lc(x), l, m, ql, qr);
    } 
    if (m<qr) {
        rt += query(rc(x), m+1, r, ql, qr);
    }
    return rt;
}

可持久化线段树,用于实现可持久化数组

#include <iostream>
using namespace std;

#define N 1000005
#define lc(x) tr[x].l
#define rc(x) tr[x].r
int n, m, a[N];
struct node {
    int l, r;
    int v;
} tr[N * 25];

// 初始2n个节点,n次修改,每次最多增加logn+1个节点,共n(logn+3)节点

int root[N], idx;

void build(int& x, int l, int r) {
    x = ++idx;
    if (l == r) {
        tr[x].v = a[l];
        return;
    }
    int m = l + r >> 1;
    build(lc(x), l, m);
    build(rc(x), m + 1, r);
}
void modify(int& x, int y, int l, int r, int pos, int v) {
    x = ++idx;
    tr[x] = tr[y];
    if (l == r) {
        tr[x].v = v;
        return;
    }
    int mid = l + r >> 1;
    if (pos <= mid)
        modify(lc(x), lc(y), l, mid, pos, v);
    else
        modify(rc(x), rc(y), mid + 1, r, pos, v);
}
int query(int x, int l, int r, int pos) {
    if (l == r)
        return tr[x].v;
    int mid = l + r >> 1;
    if (pos <= mid)
        return query(lc(x), l, mid, pos);
    else
        return query(rc(x), mid + 1, r, pos);
}
int main() {
    int ver, op, x, y;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
        scanf("%d", a + i);
    build(root[0], 1, n);
    for (int i = 1; i <= m; i++) {
        scanf("%d%d", &ver, &op);
        if (op == 1) {
            scanf("%d%d", &x, &y);
            modify(root[i], root[ver], 1, n, x, y);
        } else {
            scanf("%d", &x);
            printf("%d\\n", query(root[ver], 1, n, x));
            root[i] = root[ver];
        }
    }
}

可持久化权值线段树,区间第k小

#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;

// 每个前缀[1,i]都构建一颗权值线段树
// 查区间[l,r]的第k小,只需将版本r和版本l-1的值做差,找到前缀和为k的下标t,则t就是第k小。

#define N 200005
#define lc(x) tr[x].l
#define rc(x) tr[x].r
struct node {
    int l, r, s;  // s:节点值域中有多少个数
} tr[N * 20];
// n(logn+3)

int root[N], idx;
int n, m, a[N];
vector<int> b;

void build(int& x, int l, int r) {
    x = ++idx;
    if (l == r)
        return;
    int m = l + r >> 1;
    build(lc(x), l, m);
    build(rc(x), m + 1, r);
}
void insert(int x, int& y, int l, int r, int k) {
    y = ++idx;
    tr[y] = tr[x];
    tr[y].s++;
    if (l == r)
        return;
    int m = l + r >> 1;
    if (k <= m)
        insert(lc(x), lc(y), l, m, k);
    else
        insert(rc(x), rc(y), m + 1, r, k);
}

int query(int x, int y, int l, int r, int k) {
    if (l == r)
        return l;
    int m = l + r >> 1;
    int s = tr[lc(y)].s - tr[lc(x)].s;
    if (k <= s)
        return query(lc(x), lc(y), l, m, k);
    else
        return query(rc(x), rc(y), m + 1, r, k - s);
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
        b.push_back(a[i]);
    }
    sort(b.begin(), b.end());
    b.erase(unique(b.begin(), b.end()), b.end());
    int bn = b.size();

    build(root[0], 1, bn);
    for (int i = 1; i <= n; i++) {
        int id = lower_bound(b.begin(), b.end(), a[i]) - b.begin() + 1;
        insert(root[i - 1], root[i], 1, bn, id);
    }
    while (m--) {
        int l, r, k;
        scanf("%d%d%d", &l, &r, &k);
        int id = query(root[l - 1], root[r], 1, bn, k) - 1;
        printf("%d\\n", b[id]);
    }
    return 0;
}