Loading

可持久化线段树

可持久化线段树

可持久化实际上是一类思想。就是在修改的时候我们先把节点复制过来然后再复制的节点上修改。其他信息不变。

由于线段树每一次修改会改变根节点到一个节点上的路径,所以每一次修改都会新建一个根节点,所以可持久化线段树并不是一个树形结构,而是一张图。

例题

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ld long double
#define ll long long
#define ull unsigned long long
#define N 20000000
#define M number
using namespace std;

const int INF=0x3f3f3f3f;

inline 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;
}

int n,m,a[N];

struct PRE_DS{
    struct rode{
        int ls,rs,val;
    };
    rode p[N];
    int root[N<<2],tail,tot;
    
    inline void pushup(int k){
        p[k].val=p[p[k].ls].val+p[p[k].rs].val;
    }
    
    inline int build(int l,int r){
        int now=++tail;
        if(l==r){
            p[now].val=a[l];
            return now;
        }
        int mid=l+r>>1;
        p[now].ls=build(l,mid);
        p[now].rs=build(mid+1,r);
        pushup(now);
        return now;
    }
    
    inline int insert(int last,int l,int r,int x,int val){
        int now=++tail;
        p[now]=p[last];
        if(l==r){
            p[now].val=val;
            return now;
        }
        int mid=l+r>>1;
        if(x<=mid) p[now].ls=insert(p[last].ls,l,mid,x,val);
        else p[now].rs=insert(p[last].rs,mid+1,r,x,val);
        pushup(now);
        return now;
    }
    
    inline int ask(int k,int x,int l,int r){
        if(l==r) return p[k].val;
        int mid=l+r>>1;
        if(x<=mid) return ask(p[k].ls,x,l,mid);
        else return ask(p[k].rs,x,mid+1,r); 
    }
};
PRE_DS ds;

int main(){
    n=read();m=read();
    for(int i=1;i<=n;i++) a[i]=read();
    ds.root[0]=ds.build(1,n);
//    printf("here\n");
    for(int i=1;i<=m;i++){
        int v=read(),op=read();
        if(op==1){
            int w=read(),val=read();
            ds.root[++ds.tot]=ds.insert(ds.root[v],1,n,w,val);
        }
        else{
            int val=read();
            printf("%d\n",ds.ask(ds.root[v],val,1,n));
            ds.root[++ds.tot]=ds.root[v];
        }
    }
    return 0;
}

注意这里我们仅仅修改了查询和插入,并大致是和线段树差不多的。

洛谷第二道模板题

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ld long double
#define ll long long
#define ull unsigned long long
#define N 700100
#define M number
using namespace std;

const int INF=0x3f3f3f3f;
const int mod=1e9;

inline 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;
}

int n,m,a[N],id[N],rk[N],t[N];

struct rode{
    int ls,rs,val;
};
rode p[N<<4];
int root[N<<2],tail,tot;

struct PRE_DS{    
    inline void pushup(int k){
        p[k].val=p[p[k].ls].val+p[p[k].rs].val;
    }
    
    inline int build(int l,int r){
        int now=++tail;
        if(l==r){
            p[now].val=0;
            return now;
        }
        int mid=l+r>>1;
        p[now].ls=build(l,mid);
        p[now].rs=build(mid+1,r);
        pushup(now);
        return now;
    }
    
    inline int insert(int last,int l,int r,int x,int val){
        int now=++tail;
        p[now]=p[last];
        if(l==r){
            p[now].val+=val;
            return now;
        }
        int mid=l+r>>1;
        if(x<=mid) p[now].ls=insert(p[last].ls,l,mid,x,val);
        else p[now].rs=insert(p[last].rs,mid+1,r,x,val);
        pushup(now);
        return now;
    }
    
    inline int ask_kth(int r1,int r2,int l,int r,int k){
        if(l==r) return l;
        int lcnt=p[p[r2].ls].val-p[p[r1].ls].val,mid=l+r>>1;
        if(lcnt>=k) return ask_kth(p[r1].ls,p[r2].ls,l,mid,k);
        else return ask_kth(p[r1].rs,p[r2].rs,mid+1,r,k-lcnt);
    }
};
PRE_DS ds;

inline int disc(){
//    printf("here\n");
    for(int i=1;i<=n;i++) t[i]=a[i];
    sort(t+1,t+n+1);
    int w=unique(t+1,t+n+1)-t-1;
    for(int i=1;i<=n;i++){
        id[i]=lower_bound(t+1,t+w+1,a[i])-t;
        rk[id[i]]=a[i];
//        printf("%d\n",rk[id[i]]);
    }
    return w;
}

signed main(){
//    freopen("P3834_6.in","r",stdin);
//    freopen("my.out","w",stdout);
    n=read(),m=read();
    for(int i=1;i<=n;i++) a[i]=read(),a[i]+=mod;
    disc();
//    printf("n:%d\n",n);
//    printf("here\n");
    root[0]=ds.build(1,n);
//    printf("here\n");
    for(int i=1;i<=n;i++) root[i]=ds.insert(root[i-1],1,n,id[i],1);
    for(int i=1;i<=m;i++){
        int l=read(),r=read(),k=read();
//        printf("%d %d %d\n",l,r,k);
//        printf("root[l-1]:%d,root[r]:%d,k:%d\n",root[l-1],root[r],k);
        printf("%d\n",rk[ds.ask_kth(root[l-1],root[r],1,n,k)]-mod);
    }
    return 0;
}

这是一道查询区间第 \(k\) 小的题,我们首先对他离散化一下,然后我们在查询的时候是建立权值线段树,区间第 \(k\) 小实际上是在线段树两个根节点之间查询第 \(k\) 小,我们在树上二分就可以。至于某个数到底在这段区间内出现了多少次,我们可以通过用右边的值减去左边来得到。然后二分的去递归左子树和右子树。

posted @ 2021-04-18 15:43  hyl天梦  阅读(46)  评论(0编辑  收藏  举报