有旋(划重点)treap可持久化

· · 题解

惊了,寒假的时候我还以为这题本来就是拿

有旋treap

写的?

今天发现其实(题解)没人这么干过?

具体来说为什么treap可以可持久化 (以下说法属于信口开河,保证完全正确)

,由于treap认子不认父(即不需要记录父节点),

而且所有操作都是自上而下(或者任意方向的单向?),

这种性质决定了一些节点能够方便地同时被两个版本共用

也就是决定了这种树可以方便地可持久化

有人说splay不能可持久化是因为它需要旋转、它的形态不停地在发生改变

其实决定性原因是在于它的操作既有从上往下,又有从下往上的(splay操作)

作个比方,你现在有这样的一条路,从左往右走
(象征可持久化树上自上而下的操作)
版本①__
       ↘
      共用部分———> 你可以很方便地从两条分路合并到中间的主路上
版本②__↗

但是如果你从右往左走

①__
   ↖
    ?<————— 你就不知道应该走哪条路了
②__↙

其实,并不是说双向的树就不能可持久化,只是代码复杂,时间效益太低

(另外容易爆空间)

而可持久化线段树、可持久化treap一般只需要多加几行代码就够了

其实寒假的时候根本没考虑这么多,看见题就莽着写

以下是我的可持久化有旋treap代码,用于可持久化的新增代码(相对于原版treap)用注释做了标记

提交记录

#include <cstdio>
#include <cstdlib>
#include <iostream>
const int INF = 0x7FFFFFFF, MAXN = 5e5+1, MPR = 100;//MPR是空间多开倍数,别问我为什么用这个奇怪的缩写

int val[MAXN*MPR], left[MAXN*MPR], right[MAXN*MPR], size[MAXN*MPR], cnt[MAXN*MPR], pri[MAXN*MPR];
int id = 1;

void refresh(int &i){//复制节点,并返回新的节点,注意此处加引用'&'的意义
    if(!i) return;
    val[id] = val[i], left[id] = left[i], right[id] = right[i], pri[id] = pri[i], size[id] = size[i], cnt[id] = cnt[i];
    i = id++;
}

void pull(int i){
    size[i] = size[left[i]] + size[right[i]] + cnt[i];
} 

void r_rotate(int &i){
    refresh(left[i]);//****//
    int lnode = left[i];
    left[i] = right[lnode];
    right[lnode] = i;
    size[lnode] = size[i];
    pull(i);
    i = lnode;
}

void l_rotate(int &i){
    refresh(right[i]);//****//
    int rnode = right[i];
    right[i] = left[rnode];
    left[rnode] = i;
    size[rnode] = size[i];
    pull(i);
    i = rnode;
}

int push_root(int i){//以节点i为模板创建新的根节点
    refresh(i);
    return i;
}
void insert(int &i, int k){
    if(!i){
        i = id++;
        val[i] = k;
        size[i] = cnt[i] = 1;
        pri[i] = rand();
        return;
    }
    size[i]++;
    if(k < val[i]){
        refresh(left[i]);//****//
        insert(left[i], k);
        if(pri[left[i]] < pri[i]) r_rotate(i);
    }else if(k > val[i]){
        refresh(right[i]);//****//
        insert(right[i], k);
        if(pri[right[i]] < pri[i]) l_rotate(i);
    }else cnt[i]++;
}

bool check(int i, int k){
    if(!i) return false;
    if(k <  val[i]) return check(left[i], k);
    if(k > val[i]) return check(right[i], k);
    return true;
} 
void remove(int &i, int k){
    if(k < val[i]) refresh(left[i]), remove(left[i], k);//****//
    else if(k > val[i]) refresh(right[i]), remove(right[i], k);
    else{
        if(cnt[i] > 1) cnt[i]--, size[i]--;
        else if(left[i] && right[i]){
            if(pri[left[i]] < pri[right[i]]) r_rotate(i);
            else l_rotate(i);
            remove(i, k);
        }else i = left[i] | right[i];
        return;
    }
    size[i]--;
}

int rank(int i, int k){
    if(!i) return 0;
    if(k <= val[i]) return rank(left[i], k);
    return size[left[i]] + cnt[i] + rank(right[i], k);
}

int at(int i, int k){
    if(size[left[i]] >= k) return at(left[i], k);
    if(size[left[i]] + cnt[i] >= k) return val[i];
    return at(right[i], k - size[left[i]] - cnt[i]);
}

int prev(int i, int k){
    if(!i) return -INF;
    if(k <= val[i]) return prev(left[i], k);
    return std::max(val[i], prev(right[i], k));
}

int nxt(int i, int k){
    if(!i) return INF;
    if(k >= val[i]) return nxt(right[i], k);
    return std::min(val[i], nxt(left[i], k));
}

int i, root[MAXN];
using namespace std;

int main(){
    int n, v, opt, x;
    scanf("%d", &n);
    for(i = 1; i <= n; i++){
        scanf("%d%d%d", &v, &opt, &x);
        switch(opt){//看不懂switch语句请不要找我
            case 1:{
                root[i] = push_root(root[v]);
                insert(root[i], x);
                break;
            }
            case 2:{
                if(check(root[v], x)){
                    root[i] = push_root(root[v]);
                    remove(root[i], x);
                }else root[i] = root[v];
                break;
            }
            case 3:{
                root[i] = root[v];
                printf("%d\n", rank(root[i], x) + 1);
                break;
            }
            case 4:{
                root[i] = root[v];
                printf("%d\n", at(root[i], x));
                break;
            }
            case 5:{
                root[i] = root[v];
                printf("%d\n", prev(root[i], x));
                break;
            }
            case 6:{
                root[i] = root[v];
                printf("%d\n", nxt(root[i], x));
                break;
            }
        }
    }
    return 0;
}

(欢迎hack!)