可持久化数据结构

可持久化简介#

可持久化数据结构保留了每一次修改的历史版本。

  • 部分可持久化:可以访问所有历史版本,但只能修改最新版本。
  • 完全可持久化:所有历史版本都可以访问和修改。

我们有一个暴力的想法,就是每次修改都将整体复制一份。这样的空间复杂度是 O(NM) 的。

可持久化为我们提供了一个思路:每一次只复制变化的部分,保留不变的部分,空间复杂度为O(N+M)

可持久化线段树#

我们每一次修改都只会使树上log2N个节点发生变化。

我们动态开点地存每一个节点,在向下递归的过程中,每遇到一个需要修改的节点,就复制一份向下更新。

最后所有节点构成了一张图[1]:

Luogu P3919 【模板】可持久化线段树 1(可持久化数组)#

单点修改,单点查询的操作,可以使用可持久化线段树实现。

实际上有不带log的做法,但是实现麻烦。

#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 1e6 + 5;
int n, m;
struct node {
    int ls, rs;
    int sum;
} t[N << 5];
int rt[N];
int tot;
int a[N];
void pushup(int k) { t[k].sum = t[t[k].ls].sum + t[t[k].rs].sum; }
int build(int l, int r) {
    int p = ++tot;
    if (l == r) {
        t[p].sum = a[l];
        return p;
    }
    const int mid = l + r >> 1;
    t[p].ls = build(l, mid), t[p].rs = build(mid + 1, r);
    pushup(p);
    return p;
}
int update(int k, int l, int r, int loc, int delta) {
    int p = ++tot;
    t[p] = t[k];
    if (l == r) {
        t[p].sum = delta;
        return p;
    }
    const int mid = l + r >> 1;
    if (loc <= mid)
        t[p].ls = update(t[p].ls, l, mid, loc, delta);
    else
        t[p].rs = update(t[p].rs, mid + 1, r, loc, delta);
    pushup(p);
    return p;
}
int query(int k, int l, int r, int pos) {
    if (l == r) return t[k].sum;
    const int mid = l + r >> 1;
    if (pos <= mid) return query(t[k].ls, l, mid, pos);
    else return query(t[k].rs, mid+1, r, pos);
}
int main(void) {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
        scanf("%d ", a + i);
    rt[0] = build(1, n);
    for (int i=1, v, op, loc, value; i<=m; i++) {
        scanf("%d%d%d", &v, &op, &loc);
        if (op == 1) {
            scanf("%d", &value);
            rt[i] = update(rt[v], 1, n, loc, value);
        } else {
            printf("%d\n", query(rt[v], 1, n, loc));
            rt[i] = rt[v];
        }
    }
    return 0;
}

Luogu P3834 【模板】可持久化线段树 2(主席树)#

我们一般称可持久化线段树为主席树。但实际上,可持久化权值线段树才是主席树。

来源其实是黄嘉泰发明的用于求静态区间第k小的算法。

  • 首先构建一棵值全为0的可持久化线段树。

  • 之后对原数组进行离散化。

  • 然后我们按照原来1n的顺序将离散化后的值,以1号版本为基准,插入到可持久化线段树内。

  • 现在我们就可以查询区间[1,R]的第k小了。对于[L,R]的第k小,我们只需去除[1,L1]即可。

#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 2e5 + 5;
int n, m;
struct node {
    int ls, rs;
    int sum;
} t[N << 5];
int rt[N];
int tot;
int a[N], b[N], len;
void discreate() {
    memcpy(b, a, sizeof(a));
    sort(b + 1, b + 1 + n);
    len = unique(b + 1, b + 1 + n) - b - 1;
}
int find(int val) { return lower_bound(b + 1, b + 1 + len, val) - b; }
int build(int l, int r) {
    int p = ++tot;
    if (l == r)
        return p;
    const int mid = l + r >> 1;
    t[p].ls = build(l, mid), t[p].rs = build(mid + 1, r);
    return p;
}
int insert(int k, int l, int r, int delta) {
    int p = ++tot;
    t[p] = t[k], t[p].sum = t[k].sum + 1;
    if (l == r)
        return p;
    const int mid = l + r >> 1;
    if (delta <= mid)
        t[p].ls = insert(t[p].ls, l, mid, delta);
    else
        t[p].rs = insert(t[p].rs, mid + 1, r, delta);
    return p;
}
int query(int x, int y, int l, int r, int k) {
    const int mid = l + r >> 1, delta = t[t[y].ls].sum - t[t[x].ls].sum;
    if (l == r)
        return l;
    if (k <= delta)
        return query(t[x].ls, t[y].ls, l, mid, k);
    else
        return query(t[x].rs, t[y].rs, mid + 1, r, k - delta);
}
int main(void) {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
        scanf("%d ", a + i);
    discreate();
    rt[0] = build(1, len);
    for (int i = 1; i <= n; ++i)
        rt[i] = insert(rt[i - 1], 1, len, find(a[i]));
    for (int l, r, rk; m; m--) {
        scanf("%d%d%d", &l, &r, &rk);
        printf("%d\n", b[query(rt[l - 1], rt[r], 1, len, rk)]);
    }
    return 0;
}

LOJ2402 [THUPC 2017] 天天爱射击 / shooting#

如果我们从子弹的角度看,我们需要知道有哪些木板还存在,并且还要将打破的木板删除。这样的暴力复杂度是难以接受的,也是极难实现的。我们考虑从木板的角度看:因为子弹按顺序打出,不会消失,所以一块木板能否被打碎只取决于包含在它这个区间内的子弹数量是否足以打破它。这就转换成了一个区间求和问题。

那我们怎么知道被哪颗子弹打碎呢?很显然就是第s颗打到它上面的子弹。

那就变成了一个静态区间第k小的问题,可以用主席树解决。

#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 4e5 + 5;
int n, m, Ans[N];
struct Query {
    int x1, x2;
    int s;
} Q[N];
int a[N];
vector<int> pos[N];
struct node {
    int ls, rs;
    int sum;
} t[N<<5];
#define ls(k) t[k].ls
#define rs(k) t[k].rs
int cnt, rt[N];
void pushup(int k) { t[k].sum = t[ls(k)].sum + t[rs(k)].sum; }
int build(int l, int r) {
    int p = ++cnt;
    if (l == r) return p;
    const int mid = l + r >> 1;
    ls(p) = build(l, mid), rs(p) = build(mid+1, r);
    return p;
}
int update(int k, int l, int r, int x) {
    int p = ++cnt;
    t[p] = t[k];
    if (l == r) {
        t[p].sum++;
        return p;
    }
    const int mid = l + r >> 1;
    if (x <= mid) ls(p) = update(ls(k), l, mid, x);
    else rs(p) = update(rs(k), mid+1, r, x);
    pushup(p);
    return p;
}
int query(int x, int y, int l, int r, int k) {
    if (!x || !y) return 0;
    const int mid = l + r >> 1, delta = t[ls(y)].sum - t[ls(x)].sum;
    if (t[y].sum - t[x].sum < k) return 0;
    if (l == r) return l;
    if (k <= delta) return query(ls(x), ls(y), l, mid, k);
    else return query(rs(x), rs(y), mid+1, r, k-delta);
}
int mx;
int main(void) {
    scanf("%d%d", &n, &m);
    for (int i=1; i<=n; i++) scanf("%d%d%d", &Q[i].x1, &Q[i].x2, &Q[i].s), mx = max(mx, Q[i].x2);
    for (int i=1; i<=m; i++) scanf("%d", &a[i]), pos[a[i]].push_back(i), mx = max(mx, a[i]);
    rt[0] = build(1, mx);
    for (int i=1; i<=mx; i++) {
        rt[i] = rt[i-1];
        for (auto j : pos[i]) {
            rt[i] = update(rt[i], 1, m, j);
        }
    }
    for (int i=1; i<=n; i++) {
        int x = query(rt[Q[i].x1-1], rt[Q[i].x2], 1, m, Q[i].s);
        Ans[x]++;
    }
    for (int i=1; i<=m; i++) printf("%d\n", Ans[i]);
    return 0;
}

CF916D Jamie and To-do List#

题目大意#

您需要维护一个数据结构,支持以下操作:

  • set s x 将字符串 s 的权值设为 x ,如果没有则加入 s
  • remove s 删除 s
  • query s 查询有多少个字符串的权值小于 s 。如果 s 不存在则输出 -1
  • undo x 撤销之前的 x 次操作。

题解#

一看到撤销就知道要可持久化。

首先用map将字符串映射为整数。

在没有undo操作的情况下,用数组维护权值,权值线段树进行查询即可。

加入undo后就将上面两个东西可持久化就可以了。

tips:不要混用cinputs,会ILE

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <map>
#include <string>
using namespace std;
#define int long long
const int N = 1e5 + 5, INF = 1e9;
int n, id;
map<string, int> mp;
int getid(string s) {
    if (mp.count(s)) return mp[s];
    else return mp[s] = ++id;
}
struct node {
    int ls, rs;
    int sum;
} t[N<<7];
int rt1[N<<1], rt2[N<<1], tot;
#define ls(k) t[k].ls
#define rs(k) t[k].rs
void pushup(int k) { t[k].sum = t[ls(k)].sum + t[rs(k)].sum; }
int update(int k, int l, int r, int x, int v) {
    int p = ++tot, mid = l + r >> 1;
    t[p] = t[k];
    if (l == r) {
        t[p].sum += v;
        return p;
    }
    if (x <= mid) ls(p) = update(ls(k), l, mid, x, v);
    else rs(p) = update(rs(k), mid+1, r, x, v);
    pushup(p);
    return p;
}
int query(int k, int l, int r, int ql, int qr) {
    if (l >= ql && r <= qr) return t[k].sum;
    int mid = l + r >> 1, res = 0;
    if (ql <= mid) res += query(ls(k), l, mid, ql, qr);
    if (qr > mid) res += query(rs(k), mid+1, r, ql, qr);
    // printf("rres=%d\n", res);
    return res;
}
string s, op;
signed main(void) {
    ios::sync_with_stdio(false);
    cin.tie(NULL), cout.tie(NULL);
    cin>>n;
    for (int i=1, num, x; i<=n; i++) {
        cin>>op;
        rt1[i] = rt1[i-1], rt2[i] = rt2[i-1];
        if (op[0] == 's') {
            cin>>s>>num;
            x = getid(s);
            int res = query(rt1[i], 1, INF, x, x);
            if (res) rt2[i] = update(rt2[i], 1, INF, res, -1);
            rt2[i] = update(rt2[i], 1, INF, num, 1);
            rt1[i] = update(rt1[i], 1, INF, x, num-res);
        } else if (op[0] == 'r') {
            cin>>s;
            x = getid(s);
            int res = query(rt1[i], 1, INF, x, x);
            rt1[i] = update(rt1[i], 1, INF, x, -res);
            if (res) rt2[i] = update(rt2[i], 1, INF, res, -1);
        } else if (op[0] == 'q') {
            cin>>s;
            x = getid(s);
            int res = query(rt1[i], 1, INF, x, x);
            if (res == 0) cout<<"-1"<<endl;
            else if (res == 1) cout<<"0"<<endl;
            else cout<<query(rt2[i], 1, INF, 1, res-1)<<endl;
        } else {
            cin>>num;
            rt1[i] = rt1[i-num-1], rt2[i] = rt2[i-num-1];
        }
        cout<<flush;
    }
    return 0;
}

可持久化平衡树#

应用的并不多,权当一个了解。利用可持久化的思路可以快速打出来,这里不再赘述。

常说的可持久化平衡树一般指的就是可持久化 Treap。

Luogu P3835 【模板】可持久化平衡树#

#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <ctime>
const int N = 5e5 + 5, INF = 2147483647;
int n, rt[N];
class FHQ_Treap {
    struct node {
        int ls, rs;
        int rd, val, siz;
    } t[N * 50];
    int cnt = 0;
#define ls(kk) t[kk].ls
#define rs(kk) t[kk].rs
  public:
    void pushup(int k) { t[k].siz = t[ls(k)].siz + t[rs(k)].siz + 1; }
    int newnode(int v) {
        int k = ++cnt;
        t[k].siz = 1;
        t[k].val = v;
        t[k].rd = rand();
        ls(k) = rs(k) = 0;
        return k;
    }
    void split(int now, int k, int &x, int &y) {
        if (!now) {
            x = y = 0;
            return;
        }
        if (t[now].val <= k) {
            x = ++cnt;
            t[x] = t[now];
            split(rs(x), k, rs(x), y);
            pushup(x);
        } else {
            y = ++cnt;
            t[y] = t[now];
            split(ls(y), k, x, ls(y));
            pushup(y);
        }
    }
    int merge(int x, int y) {
        if (!x || !y)
            return x | y;
        int p = ++cnt;
        if (t[x].rd < t[y].rd) {
            t[p] = t[x];
            rs(p) = merge(rs(p), y);
        } else {
            t[p] = t[y];
            ls(p) = merge(x, ls(p));
        }
        pushup(p);
        return p;
    }
    void ins(int &root, int v) {
        int x, y;
        split(root, v, x, y);
        root = merge(merge(x, newnode(v)), y);
    }
    void del(int &root, int v) {
        int a, b, c, d;
        split(root, v, a, b), split(a, v - 1, c, d);
        root = merge(merge(c, merge(ls(d), rs(d))), b);
    }
    int kth(int now, int k) {
        while (1) {
            if (k == t[ls(now)].siz + 1)
                return t[now].val;
            else if (k <= t[ls(now)].siz)
                now = ls(now);
            else
                k -= t[ls(now)].siz + 1, now = rs(now);
        }
    }
    int rk(int &root, int val) {
        int x, y;
        split(root, val - 1, x, y);
        int res = t[x].siz + 1;
        root = merge(x, y);
        return res;
    }
    int pre(int &root, int val) {
        int x, y, res;
        split(root, val - 1, x, y);
        if (!t[x].siz)
            res = -INF;
        else
            res = kth(x, t[x].siz);
        root = merge(x, y);
        return res;
    }
    int nxt(int &root, int val) {
        int x, y, res;
        split(root, val, x, y);
        if (!t[y].siz)
            res = INF;
        else
            res = kth(y, 1);
        root = merge(x, y);
        return res;
    }
} fhq;
int main(void) {
    srand((unsigned)time(NULL));
    scanf("%d", &n);
    for (int i = 1, op, v, x; i <= n; i++) {
        scanf("%d%d%d", &v, &op, &x);
        rt[i] = rt[v];
        if (op == 1)
            fhq.ins(rt[i], x);
        else if (op == 2)
            fhq.del(rt[i], x);
        else if (op == 3)
            printf("%d\n", fhq.rk(rt[i], x));
        else if (op == 4)
            printf("%d\n", fhq.kth(rt[i], x));
        else if (op == 5)
            printf("%d\n", fhq.pre(rt[i], x));
        else
            printf("%d\n", fhq.nxt(rt[i], x));
    }
    return 0;
}

可持久化 Trie#

每一次新插入字符串就新建一个根。

Luogu P4735 最大异或和#

咕咕咕咕咕

#include <bits/stdc++.h>
using namespace std;
const int N = 6e5 + 7;
int trie[N * 24][2], lst[N * 24];
int s[N], rt[N], n, m, tot;
void insert(int i, int k, int p, int q) {
    if (k < 0) {
        lst[q] = i;
        return;
    }
    int c = s[i] >> k & 1;
    if (p)
        trie[q][c ^ 1] = trie[p][c ^ 1];
    trie[q][c] = ++tot;
    insert(i, k - 1, trie[p][c], trie[q][c]);
    lst[q] = max(lst[trie[q][0]], lst[trie[q][1]]);
}

int ask(int now, int val, int k, int lim) {
    if (k < 0)
        return s[lst[now]] ^ val;
    int c = val >> k & 1;
    if (lst[trie[now][c ^ 1]] >= lim)
        return ask(trie[now][c ^ 1], val, k - 1, lim);
    else
        return ask(trie[now][c], val, k - 1, lim);
}

int main(void) {
    cin >> n >> m;
    lst[0] = -1;
    rt[0] = ++tot;
    insert(0, 23, 0, rt[0]);
    for (int i = 1, x; i <= n; i++) {
        scanf("%d", &x);
        s[i] = s[i - 1] ^ x;
        rt[i] = ++tot;
        insert(i, 23, rt[i - 1], rt[i]);
    }
    for (int i = 1; i <= m; i++) {
        char opt[2];
        scanf("%s", opt);
        if (opt[0] == 'A') {
            int x;
            scanf("%d", &x);
            rt[++n] = ++tot;
            s[n] = s[n - 1] ^ x;
            insert(n, 23, rt[n - 1], rt[n]);
        } else {
            int l, r, x;
            scanf("%d%d%d", &l, &r, &x);
            printf("%d\n", ask(rt[r - 1], x ^ s[n], 23, l - 1));
        }
    }
    return 0;
}

BZOJ 2741 【FOTILE模拟赛】L#

分块+可持久化 01 Trie

#include <algorithm>
#include <cmath>
#include <cstdio>
using namespace std;
#define int long long
const int N = 12500;
int len, blo[N], f[120][N];
int rt[N*32], ch[N*32][2], lst[N*32], cnt;
int n, m, a[N];
void ins(int now, int pre, int val) {
    int u = rt[now] = ++cnt, v = rt[pre];
    for (int i = 30; i >= 0; i--) {
        int x = val >> i & 1;
        ch[u][x ^ 1] = ch[v][x ^ 1];
        u = ch[u][x] = ++cnt, v = ch[v][x];
        lst[cnt] = now + 1;
    }
}
int ask(int l, int r, int val) {
    int u = rt[r], res = 0;
    for (int i = 30; i >= 0; i--) {
        int x = val >> i & 1;
        if (lst[ch[u][x ^ 1]] >= l + 1)
            res |= 1 << i, u = ch[u][x ^ 1];
        else
            u = ch[u][x];
    }
    return res;
}
signed main(void) {
    scanf("%lld%lld", &n, &m);
    len = sqrt(n);
    ins(0, 0, 0);
    blo[0] = 1;
    for (int i = 1; i <= n; i++) {
    	blo[i] = (i-1) / len + 1;
        scanf("%lld", a + i);
        a[i] ^= a[i - 1];
        ins(i, i - 1, a[i]);
    }
    for (int i = len; i <= n; i += len) {
        int k = i / len;
        for (int j = i + 1; j <= n; j++)
            f[k][j] = max(f[k][j - 1], ask(i, j - 1, a[j]));
    }
    int l, r, l2, ans = 0;
    while (m--) {
        scanf("%lld%lld", &l, &r);
        l = (l + ans) % n + 1, r = (r + ans) % n + 1;
        if (l > r)
            swap(l, r);
        l--, ans = 0;
        l2 = min(blo[l] * len, r);
        for (int i = l; i <= l2; i++)
            ans = max(ans, ask(l, r, a[i]));
        ans = max(ans, f[blo[l]][r]);
        printf("%lld\n", ans);
    }
}
posted @   LewisLi  阅读(168)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示
主题色彩