【数据结构:进阶】可持久化数据结构

珂朵莉树

features

  • 暴力,实现简单
  • 本质思想是分块,相同值连在一起的算一块,用set维护
  • 适用于大区间赋值问题(若每次赋值的区间很小,则退化)
  • 容易被卡(

code

cf915E 动态开点/珂朵莉树
注意要开快读快输

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

struct node{
    ll l,r;
    mutable int v;
    node(ll l, ll r, int v):l(l),r(r),v(v){}
    bool operator<(const node &o) const {return l<o.l;}
};

set<node> tree;

auto split(ll pos){
    auto it = tree.lower_bound(node(pos,0,0));
    if(it!=tree.end()&&it->l == pos) return it;
    it--;
    ll l = it->l,r=it->r;int v=it->v;
    tree.erase(it);
    tree.insert(node(l,pos-1,v));
    return tree.insert(node(pos,r,v)).first;
}

int ans;

void assign(ll l, ll r, int v){
    auto end = split(r+1),begin = split(l),it=begin;
    int len = 0,workdays = 0;
    for(;it!=end;++it){
        len += it->r-it->l+1;
        workdays += (it->r-it->l+1)*(it->v-1);
    }
    tree.erase(begin,end);
    tree.insert(node(l,r,v));
    if(v-1){
        ans += len - workdays;
    }else{
        ans -= workdays;
    }
}

inline int read(){
    int j=1,k=0;
    char ch = getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') j=-1;ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        k=k*10+ch-'0';ch=getchar();
    }
    return k*j;
}

inline void print(int x){
    if(x < 10) putchar('0' + x); else print(x / 10), print(x % 10);
}
int main(){
    int n,q;n=read(),q=read();
    tree.insert(node(1,n,2));
    ans = n;
    while(q--){
        int l = read(),r = read(),k = read();
        assign(l,r,k);
        print(ans);
        putchar(10);
    }
}

cf896c 珂朵莉树源题


#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
inline int read() {int f=1,x=0;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return f*x;}
inline void print(ll x) {if(x<10)putchar('0'+x);else print(x/10),print(x%10);}
ll seed,n,m,vmax;
int op;
ll x,y,l,r;
ll a[100004];


struct node{
    ll l,r;
    mutable ll v;
    node(ll l,ll r,ll v):l(l),r(r),v(v){}
    bool operator<(const node &o) const{return l<o.l;}
};

set<node> tree;

auto split(ll pos){
    auto it = tree.lower_bound(node(pos,0,0));
    if(it!=tree.end()&&it->l==pos) return it;
    it--;
    ll l=it->l,r=it->r,v=it->v;
    tree.erase(it);
    tree.insert(node(l,pos-1,v));
    return tree.insert(node(pos,r,v)).first;
}

ll qpow(ll a,ll n,ll y){
    ll ans = 1;
    a%=y;
    while(n){
        if(n&1) ans=ans*a%y;
        a=a*a%y;
        n>>=1;
    }
    return ans;
}

ll assign(ll l,ll r,int op,ll v,ll y){
    auto end = split(r+1),begin = split(l),it=begin;
    if(op==1){
        // add
        for(;it!=end;++it){
            it->v+=v;
        }
    }else if(op==2){
        tree.erase(begin,end);
        tree.insert(node(l,r,v));
    }else if(op==3){
        vector<pair<ll,ll>> nod; // 节点值和区间长度
        for(;it!=end;++it) nod.push_back(make_pair(it->v,it->r-it->l+1));
        sort(nod.begin(),nod.end());
        for(int i=0;i<nod.size();++i){
            v -= nod[i].second;
            if(v<=0) return nod[i].first;
        }
    }else{
        ll ans = 0;
        for(;it!=end;++it){
            ans = (ans+(it->r-it->l+1)*qpow(it->v,x,y)%y)%y;
        }
        return ans;
    }
    return 0;
}

int rnd(){
    ll ret = seed;
    seed = (seed*7+13)%1000000007;
    return ret;
}

void generate(){
    for(int i=1;i<=n;++i){
        a[i] = (rnd()%vmax)+1;
        tree.insert(node(i,i,a[i]));
    }
    for(int i=1;i<=m;++i){
        op = (rnd()%4)+1;
        l = (rnd()%n)+1;
        r = (rnd()%n)+1;

        if(l>r) swap(l,r);
        if(op==3) x=(rnd()%(r-l+1))+1;
        else x=(rnd()%vmax)+1;
        if(op==4) y=(rnd()%vmax)+1;

        if(op==1){
            assign(l,r,1,x,0);
        }else if(op==2){
            assign(l,r,2,x,0);
        }else if(op==3){
            print(assign(l,r,3,x,0));
            putchar(10);
        }else print(assign(l,r,4,x,y)),putchar(10);
    }
}

int main(){
    n=read(),m=read(),seed=read(),vmax=read();
    generate();
}

线段树进阶

动态开点

cf 915e

  • 空间复杂度: O ( m l o g m ) O(mlogm) O(mlogm)
#include <bits/stdc++.h>
using namespace std;
#define ls(x) tree[x].ls
#define rs(x) tree[x].rs
#define val(x) tree[x].val
#define lzy(x) tree[x].lzy
const int maxm = 15000004;
int cnt;

struct{
    int ls,rs,val,lzy;
}tree[maxm]; // 能开多大开多大

void push_up(int rt){
    val(rt) = val(ls(rt))+val(rs(rt));
}

void push_down(int rt,int m){
    if(!ls(rt)) ls(rt) = ++cnt;
    if(!rs(rt)) rs(rt) = ++cnt;
    if(lzy(rt)){
        lzy(ls(rt)) = lzy(rt);
        lzy(rs(rt)) = lzy(rt);
        val(ls(rt)) = (lzy(rt)-1)*(m-(m>>1));
        val(rs(rt)) = (lzy(rt)-1)*(m>>1);
        lzy(rt) = 0;
    }
}

void update(int a,int b,int c,int l,int r,int rt){
  //  cout<<a<<" "<<b<<" "<<l<<" "<<r<<" "<<rt<<" "<<val(rt)<<" "<<lzy(rt)<<endl;
    if(a<=l&&b>=r){
        val(rt) = c*(r-l+1);
        lzy(rt) = c+1;
        return;
    }
    push_down(rt,r-l+1);
    int mid = (l+r)>>1;
    if(a<=mid) update(a,b,c,l,mid,ls(rt));
    if(b>mid) update(a,b,c,mid+1,r,rs(rt));
    push_up(rt);
}

int main(){
    int n,q;scanf("%d%d",&n,&q);
    val(1)=n; // 初始化第一个节点
    lzy(1)=2;
    cnt++;
    for(int i=1;i<=q;++i){
        int l,r,k;scanf("%d%d%d",&l,&r,&k);
        k--;
        update(l,r,k,1,n,1);
        printf("%d\n",val(1));
    }
}

权值线段树

普通线段树:维护一个区间中的数的属性。
权值线段树:维护一个区间中,在一个值域中的数的数目。
就是用线段树来维护一个桶。没什么特殊的。很容易实现。

可持久化线段树

可持久化数组(可持久化线段树)

  • 区间修改:标记永久化

洛谷3919 【模板】可持久化线段树1

#include<bits/stdc++.h>
using namespace std;
int cnt=1;
const int maxn = 5e6+5;
int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*f;}
#define ls(x) tree[x].ls
#define rs(x) tree[x].rs
#define val(x) tree[x].val
struct{
    int val,ls,rs;
}tree[maxn<<2];
// 单点修改,单点查询:可持久化数组
int arr[maxn],root[maxn];

void build(int l,int r,int rt){
    if(l==r) val(rt) = arr[l];
    else {
        ls(rt) = ++cnt,rs(rt)=++cnt;
        int mid = (l+r)>>1;
        build(l,mid,ls(rt));
        build(mid+1,r,rs(rt));
    }
}

void update(int a,int l,int r,int v,int rt,int nrt){
    if(l==r) val(nrt) = v;
    else{
        ls(nrt) = ls(rt),rs(nrt)=rs(rt);
        int mid = (l+r)>>1;
        if(a<=mid) ls(nrt)=++cnt,update(a,l,mid,v,ls(rt),ls(nrt));
        else rs(nrt) = ++cnt,update(a,mid+1,r,v,rs(rt),rs(nrt));
    }
}

int query(int l,int r,int a,int rt){
    if(l==r) return val(rt);
    else {
        int mid = (l+r)>>1;
        if(a<=mid) return query(l,mid,a,ls(rt));
        else return query(mid+1,r,a,rs(rt));
    }
}

int main(){
    int n,m;scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i){
        scanf("%d",&arr[i]);
    }
    build(1,n,1);
    root[0]=1;
    for(int i=1;i<=m;++i){
        int v,id,loc,val;
        scanf("%d%d",&v,&id);
        if(id==1){
            scanf("%d%d",&loc,&val);
            root[i] = ++cnt;
            update(loc,1,n,val,root[v],root[i]);
        }else{
            scanf("%d",&loc);
            root[i] = root[v];
            printf("%d\n",query(1,n,loc,root[v]));
        }
    }
}

hdu 4348 可持久化线段树(区间修改+查询)

#include<bits/stdc++.h>
using namespace std;

const int maxn = 5e6+5;
int cnt = 1;
int arr[maxn],root[maxn];
typedef long long ll;

#define ls(x) tree[x].ls
#define rs(x) tree[x].rs
#define val(x) tree[x].val
#define lzy(x) tree[x].lzy

struct{
    ll val;
    int ls,rs;
    ll lzy;
}tree[maxn<<2];

void build(int l,int r,int rt){
    if(l==r){
        val(rt) = arr[l];
        return;
    }
    ls(rt) = ++cnt,rs(rt) = ++cnt;
    int mid = (l+r)>>1;
    build(l,mid,ls(rt));
    build(mid+1,r,rs(rt));
    val(rt) = val(ls(rt))+val(rs(rt));
}

void update(int a,int b,ll c,int l,int r,int rt,int nrt){
    ls(nrt) = ls(rt), rs(nrt) = rs(rt), lzy(nrt) = lzy(rt);
    if (b>=r&&a<=l) {
        lzy(nrt) += c;
    }else{
        int mid = (l+r)>>1;
        if(a<=mid) ls(nrt) = ++cnt, update(a,b,c,l,mid,ls(rt),ls(nrt));
        if(b>mid) rs(nrt) = ++cnt, update(a,b,c,mid+1,r,rs(rt),rs(nrt));
    }
    val(nrt) = val(rt) + (min(b,r) - max(a,l)+1)*c;
}

ll query(int a,int b,int l,int r,int rt,ll mk){ // 标记永久化
    if(b>=r&&a<=l) return val(rt) + mk*(r-l+1);
    int mid = (l+r)>>1;
    ll res = 0;
    if(a<=mid) res += query(a,b,l,mid,ls(rt),mk+lzy(rt));
    if(b>mid) res += query(a,b,mid+1,r,rs(rt),mk+lzy(rt));
    return res;
}

int main(){
    int n,m;
    int isf = 1;
    while(~scanf("%d%d",&n,&m)){
        if(!isf) cout<<endl;
        isf=0;
        for(int i=1;i<=n;++i)
            scanf("%d",&arr[i]);
        root[0]=1;
        build(1,n,1);
        int time = 0; // the current time stamp
        for(int i=1;i<=m;++i){
            string s;cin>>s;
            if(s=="C"){
                int l,r,d;scanf("%d%d%d",&l,&r,&d);
                root[++time] = ++cnt;
                update(l,r,d,1,n,root[time-1],root[time]);
            }else if(s=="Q"){
                int l,r;scanf("%d%d",&l,&r);
                cout<<query(l,r,1,n,root[time],0)<<endl;
            }else if(s=="H"){
                int l,r,t;scanf("%d%d%d",&l,&r,&t);
                cout<<query(l,r,1,n,root[t],0)<<endl;
            }else{
                int t;scanf("%d",&t);
                time = t;
            }
        }
    }
}

主席树

洛谷3834 可持久化线段树【主席树】
区间第K大。
点查询、点修改。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e6+5;
int A[maxn],root[maxn],cnt=1;
int c[maxn],L[maxn],ori[maxn];

#define num(x) tree[x].num
#define ls(x) tree[x].ls
#define rs(x) tree[x].rs

struct{
    int num;
    int ls,rs;
}tree[maxn<<2];

void build(){
    root[0] = 1;
    num(1) = 0;
    ls(1)=1;
    rs(1)=1;
}

void update(int a,int l,int r,int rt,int nrt){
    num(nrt) = num(rt);
    if(l==r) num(nrt)++;
    else{
        ls(nrt) = ls(rt),rs(nrt) = rs(rt);
        int mid = (l+r)>>1;
        if(a<=mid) ls(nrt)=++cnt,update(a,l,mid,ls(rt),ls(nrt));
        else rs(nrt)=++cnt,update(a,mid+1,r,rs(rt),rs(nrt));
        num(nrt) = num(ls(nrt)) + num(rs(nrt));
    }
}

// 查询区间第a大
int query(int a,int l,int r,int p,int q){
  //  cout<<a<<" "<<l<<" "<<r<<" "<<p<<" "<<q<<endl;
    if(l==r) return l;
    else{
        int mid = (l+r)>>1;
        if(a<=num(ls(q))-num(ls(p))) return query(a,l,mid,ls(p),ls(q));
        else return query(a-(num(ls(q))-num(ls(p))),mid+1,r,rs(p),rs(q));
    }
}

int discretize(int n){
    for(int i=0;i<n;++i) c[i]=A[i];
    sort(c,c+n);
    int len = unique(c,c+n)-c;
    int maxx = 0;
    for(int i=0;i<n;++i){
        L[i] = lower_bound(c,c+len,A[i])-c+1; // 查找
        maxx = max(maxx,L[i]);
        ori[L[i]] = A[i]; // 离散化后的数对应的原数
    }
    return maxx;
}

int main(){
    int n,m;scanf("%d%d",&n,&m);
    for(int i=0;i<n;++i) scanf("%d",&A[i]);
    build();
    int maxx = discretize(n);

    for(int i=0;i<n;++i){
        root[i+1] = ++cnt;
        update(L[i],1,maxx,root[i],root[i+1]);
    }

    for(int i=1;i<=m;++i){
        int l,r,k;scanf("%d%d%d",&l,&r,&k);
        int id = query(k,1,maxx,root[l-1],root[r]);
        cout<<ori[id]<<endl;
    }
}

可持久化平衡树

无旋Treap

码量小,常数小。
除了LCT问题亚于splay,其他方面都可以替代其他平衡树。
核心操作:split和merge。其他操作都是基于这两个操作。
loj104 普通平衡树

#include<bits/stdc++.h>
using namespace std;
const int maxn = 4e5+6;

struct node{
    int key,lch,rch,priority,sz;
    node(int k=0,int l=0,int r=0,int p=0,int s=0):key(k),lch(l),rch(r),priority(p),sz(s){}
}tree[maxn<<2];
int cnt;
int root;  // 根
// 小根堆
// 可重

#define key(x) tree[x].key
#define pri(x) tree[x].priority
#define lch(x) tree[x].lch
#define rch(x) tree[x].rch
#define sz(x) tree[x].sz

int newnode(int v){
    tree[++cnt] = node(v,0,0,rand(),1);
    return cnt;
}

void push_up(int u){
    sz(u) = sz(lch(u)) + sz(rch(u))+1;
}

//   分裂成两个treap,第一个treap所有节点的关键值小于等于key
//   第二个treap所有节点的关键值大于key
pair<int,int> split(int u,int key){
    if(!u) return make_pair(0,0);
    if(key<key(u)) {
        pair<int,int> o = split(lch(u),key);
        lch(u) = o.second;
        push_up(u);
        return make_pair(o.first,u);
    }else{
        pair<int,int> o = split(rch(u),key);
        rch(u) = o.first;
        push_up(u);
        return make_pair(u,o.second);
    }
}

//  现有两棵treap,满足u中所有的key小于等于v中所有的key。我们现在将他们两个合并。
int merge(int u,int v){
    if(!u||!v) return u+v;
    if(pri(u)<pri(v)){
        rch(u) = merge(rch(u),v);
        push_up(u);
        return u;
    }else{
        lch(v) = merge(u,lch(v));
        push_up(v);
        return v;
    }
}

//  查询排名为k的点
int find_rank(int u,int k){
    if(!u) return 0;
    if(k<=sz(lch(u))) return find_rank(lch(u),k);
    else if(k==sz(lch(u))+1) return u;
    else return find_rank(rch(u),k-sz(lch(u))-1);
}

//  查询值为key的排名,若有多个,输出最小排名
int get_rank(int u,int key){
    pair<int,int> o = split(u,key-1);
    int num = sz(o.first)+1;
    root = merge(o.first,o.second);
    return num;
}

void insert(int key){
    pair<int,int> o = split(root,key);
    o.first = merge(o.first,newnode(key));
    root = merge(o.first,o.second);
}

//  删除一个键值为key的点
void erase(int key){
    pair<int,int> o = split(root,key);   //   左:小于等于Key,右:大于key
    pair<int,int> p = split(o.first,key-1); // 左:小于等于key-1,右:等于key
    p.second = merge(lch(p.second),rch(p.second));  // 删掉根节点
    root = merge(p.first,merge(p.second,o.second));
}

int get_precursor(int key){
    pair<int,int> o = split(root,key-1);
    int num = key(find_rank(o.first,sz(o.first)));
    merge(o.first,o.second);
    return num;
}

int get_successor(int key){
    pair<int,int> o = split(root,key);
    int num = key(find_rank(o.second,1));
    root = merge(o.first,o.second);
    return num;
}

int main(){
    int n;scanf("%d",&n);
    srand(time(NULL));
    for(int i=1;i<=n;++i){
        int opt,x;scanf("%d%d",&opt,&x);
        switch (opt) {
            case 1:insert(x);break;
            case 2:erase(x);break;
            case 3:cout<<get_rank(root,x)<<endl;break;
            case 4:cout<<key(find_rank(root,x))<<endl;break;
            case 5:cout<<get_precursor(x)<<endl;break;
            case 6:cout<<get_successor(x)<<endl;break;
        }
    }
}

可持久化

在两个核心操作上加点即可。

pair<int,int> split(int u,int key){
    if(!u) return make_pair(0,0);
    int nrt = ++cnt;
    tree[nrt] = tree[u];
    if(key<key(u)) {
        pair<int,int> o = split(lch(u),key);
        lch(nrt) = o.second;
        push_up(nrt);
        return make_pair(o.first,nrt);
    }else{
        pair<int,int> o = split(rch(u),key);
        rch(nrt) = o.first;
        push_up(nrt);
        return make_pair(nrt,o.second);
    }
}

int merge(int u,int v){
    if(!u||!v) return u+v;
    int nrt = ++cnt;
    if(pri(u)<pri(v)){
        tree[nrt] = tree[u];
        rch(nrt) = merge(rch(u),v);
    }else{
        tree[nrt] = tree[v];
        lch(nrt) = merge(u,lch(v));
    }
    push_up(nrt);
    return nrt;
}

然后你会发现如果权值相同的点很多,treap会退化。。。。
此时有一种神奇的方法,通过概率进行合并。
来源:Bili Young’s blog
在这里插入图片描述
模板题:
洛谷P3835 可持久化平衡树

可持久化并查集

洛谷模板
思路很简单,就是在普通并查集上面叠加了大于log的时间复杂度。
就是把fa数组构建成一棵树(可持久化线段树(可持久化数组))。点修改点查询。为什么不用数组(查询 O ( 1 ) O(1) O(1)修改 O ( 1 ) O(1) O(1)),废话,当然是因为要可持久化啦。
合并方式也从路径压缩改成按秩合并。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值