可持久化线段树/平衡树

可持久化:

指的是我们每对树做一次修改,就将其保存为一个历史版本,在以后的询问/修改中,我可以选择任意一个历史版本来询问/修改

实现:

最简单的实现思路:

最简单的思路当然是每次修改的时候都将历史版本的树copy一遍,然后对其进行修改并保存,但是这样的话,如果要留存的历史版本很多而且树也很大,很容易就会MLE

正确的实现思路:

对于一棵树来说,我们每次单点修改的时候,无非就是修改了它的左子树或者是右子树,那么对于新的版本来说,当他修改的是左子树的节点的时候,显然我们可以继续沿用历史版本的右子树,也就是只要复制并修改一棵左子树就可以了;当他修改的是右子树的节点的时候,显然我们可以继续沿用历史版本的左子树,也就是只要复制并修改一棵右子树就可以了。这就是可持久化的核心思想

实现方式:

我们每访问一个历史版本的结点的位置的时候,都先新开一个对应的结点,并让这个节点继承历史版本的左子树和右子树,然后根据我们需要修改的位置去递归他的左子树或者右子树。假设我们递归的是左子树,这样一来,当我们一访问需要更新的左子树的时候,我们又会开了一个新的结点,就达到了开新树的目的,同时,没有递归的右子树就是继承了原来历史版本的旧子树。

可持久化线段树: 

题目链接:P3919 【模板】可持久化线段树 1(可持久化数组) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

实现代码:

#include <iostream>
#include <cstdio>
using namespace std;
const int N = 1E6 + 10;
int n, m;
#define mid (l+r>>1)
int ls[N * 55], rs[N * 55], val[N * 55];
int root[N],idx;
int a[N];

//点修点查不需要pushup
void build(int &u,int l,int r){
    //动态开点
    if(!u){
        u = ++idx;
    }
    if(l==r){
        val[u] = a[l];
        return;
    }
    build(ls[u], l, mid);
    build(rs[u], mid + 1, r);
}

void change(int v,int &u,int l,int r,int p,int k){
    //开新结点
    u = ++idx;
    //递归到了叶子结点直接修改
    if(l==r){
        val[u] = k;
        return;
    }
    //非叶子结点继承左右子树
    ls[u] = ls[v];
    rs[u] = rs[v];
    //递归要修改的子树
    if(p<=mid){
        change(ls[v], ls[u], l, mid, p, k);
    }
    else{
        change(rs[v], rs[u], mid + 1,r, p, k);
    }
}

//和正常的点查一样
int query(int u,int l,int r,int p){
    if(l==r){
        return val[u];
    }
    if(p<=mid){
        return query(ls[u], l, mid, p);
    }
    else{
        return query(rs[u], mid + 1,r, p);
    }
}

int main(){
    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,v,op,p,value; i <= m;i++){
        scanf("%d%d", &v, &op);
        if(op==1){
            scanf("%d%d", &p, &value);
            change(root[v], root[i], 1, n, p, value);
        }
        else{
            scanf("%d", &p);
            //这里也要生成一个新的版本
            //一开始漏了这句话,只有24pts
            root[i] = root[v];
            printf("%d\n", query(root[v], 1, n, p));
        }
    }
    return 0;
}

可持久化平衡树:

题目链接:P3835 【模板】可持久化平衡树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

操作和可持久化线段树类似,我这里采用的是FHQ-Treap实现的平衡树,在split的过程中会修改树的值,因此在split的时候需要开新点,由于merge的时候连接的都是split开的新点,所以merge就不用再开新点了

实现代码如下:

#include <iostream>
#include <climits>
#include <time.h>
using namespace std;
const int N = 5E5 + 10;
struct node
{
    int l, r;
    int key;
    int val;
    int size;
} tr[N * 50];
int idx, root[N];
int newnode(int v)
{
    tr[++idx].val = v;
    tr[idx].key = rand();
    tr[idx].size = 1;
    return idx;
}
void pushup(int p)
{
    tr[p].size = tr[tr[p].l].size + tr[tr[p].r].size + 1;
}
// split更新裂痕的时候开新树
void split(int p, int v, int &x, int &y)
{
    if (!p)
    {
        x = y = 0;
        return;
    }
    if (tr[p].val <= v)
    {
        x = ++idx;
        tr[x] = tr[p];
        split(tr[x].r, v, tr[x].r, y);
        pushup(x);
    }
    else
    {
        y = ++idx;
        tr[y] = tr[p];
        split(tr[y].l, v, x, tr[y].l);
        pushup(y);
    }
}
//split的时候,更新的裂痕结点开了新结点
//在merge的时候,只有裂痕结点会发生连接,则不需要再开新结点
int merge(int x, int y)
{
    if (!x || !y)
    {
        return x + y;
    }
    if (tr[x].key < tr[y].key)
    {
        tr[x].r = merge(tr[x].r, y);
        pushup(x);
        return x;
    }
    else
    {
        tr[y].l = merge(x, tr[y].l);
        pushup(y);
        return y;
    }
}
void insert(int &rt, int v)
{
    int x, y;
    split(rt, v, x, y);
    rt = merge(merge(x, newnode(v)), y);
}
void del(int &rt, int v)
{
    int x, y, z;
    split(rt, v, x, y);
    split(x, v - 1, x, z);
    z = merge(tr[z].l, tr[z].r);
    rt = merge(merge(x, z), y);
}
int getk(int &rt, int v)
{
    int x, y;
    split(rt, v - 1, x, y);
    int ans = tr[x].size + 1;
    rt = merge(x, y);
    return ans;
}
int getval(int p, int k)
{
    if (k == tr[tr[p].l].size + 1)
    {
        return tr[p].val;
    }
    else if (k <= tr[tr[p].l].size)
    {
        return getval(tr[p].l, k);
    }
    else
    {
        return getval(tr[p].r, k - tr[tr[p].l].size - 1);
    }
}
int getpre(int &rt, int v)
{
    int x, y;
    split(rt, v - 1, x, y);
    int z = x;
    if (!z)
    {
        return INT_MIN + 1;
    }
    while (tr[z].r)
    {
        z = tr[z].r;
    }
    rt = merge(x, y);
    return tr[z].val;
}
int getnxt(int &rt, int v)
{
    int x, y;
    split(rt, v, x, y);
    int z = y;
    if (!z)
    {
        return INT_MAX;
    }
    while (tr[z].l)
    {
        z = tr[z].l;
    }
    rt = merge(x, y);
    return tr[z].val;
}
// 还能优化:
// 操作数为3,4,5,6时,不需要copy一份新树
int main()
{
    int n;
    cin >> n;
    srand((unsigned int)time(NULL));
    for (int i = 1, v, opt, x; i <= n; i++)
    {
        cin >> v >> opt >> x;
        root[i] = root[v];
        if (opt == 1)
        {
            insert(root[i], x);
        }
        else if (opt == 2)
        {
            del(root[i], x);
        }
        else if (opt == 3)
        {
            cout << getk(root[i], x) << endl;
        }
        else if (opt == 4)
        {
            cout << getval(root[i], x) << endl;
        }
        else if (opt == 5)
        {
            cout << getpre(root[i], x) << endl;
        }
        else if (opt == 6)
        {
            cout << getnxt(root[i], x) << endl;
        }
    }
    return 0;
}

  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值