可持久化数据结构

思想

以下为个人的理解,可供参考

考虑到一次修改只会影响到一个节点,那我们就直接另存一下这个节点就好了呗

但是不行,因为访问时我询问时间为 t 的节点,我找不到 t 时间这个点的改动什么的

而线段树有一个好的性质,就是它修改一个节点最多会动log个节点,其余的节点都是以前的,所以可以每一次修改,就把变化了的节点新开一棵树,以前的没有动的节点,就直接把旧节点连在新节点上就行

然后操作时你就把一个时间内的树考虑成一颗独立的树就行,其它的节点并不会产生任何影响

新思想

在写博客时,我就想,既然我们只是做不到查询t时间点修改了哪些位置

对于可持久化数组,我对每个节点开一个 vector 存储的是它变化的时间位置,对于每次查询 \(t\) 时间 \(x\) 上的位置,直接二分,找到最后一次修改,这样不行吗?

然而考虑题目是在历史版本上进行修改,也就是历史版本它也会有别的修改,你无法全部统计入档次修改,假了

主席树

用途:会在线段树上进行很多次修改,还会查询历史节点信息

然后考虑到修改一个节点,只会改变其下的log个节点,于是就新整了log个节点,建立在原树之上

image

m 次修改操作,相当于是建立了 \(m\) 棵树,试想一下,你从修改一节点往下进行查询,是不是依旧是一棵树呢

代码实现(真的不难):

void change(int k, int l, int r, int x,int v,int &g) {
        k = newnode(k);
        g = k;
        if (l == r) {
            sum(k)=v;
            return;
        }
        int mid = (l + r) >> 1;
        if (x <= mid)
            change(ls(k), l, mid, x, ls(k));
        else
            change(rs(k), mid + 1, r, x, rs(k));
        sum(k) = sum(ls(k)) + sum(rs(k));
    }
change(top[i-1],1,n,i,a[i],top[i]);//top[i-1]表示上一次修改的根节点,top[i]表示这次的根节点

T1:

区间下标为时间节点,线段树维护值域

先离散化(因为要线段树维护)

查询,就当作正常线段树上二分即可,为什么要 \(l-1,r\) 端点一起进行query呢?

因为线段树上二分,我们只知道你在这个值域区间里排老几,而想求这个值域区间有多少数,我们只能通过前缀和,同时求前缀和后缀的方法

#include<bits/stdc++.h>
#define ls(x) tr[x].ls
#define rs(x) tr[x].rs
#define sum(x) tr[x].sum
using namespace std;
const int N=2e5+5;
struct dot{
    int ls,rs,sum;
}tr[N*40];
int cnt,n,m,len;
int a[N],st[N];
vector<int>tmp;
struct tree{
    int newnode(int k){
        tr[++cnt]=tr[k];
        return cnt;
    }
    void change(int k,int l,int r,int x,int &g){
        k=newnode(k);
        g=k;
        if(l==r){
            sum(k)++;
            return;
        }
        int mid=(l+r)>>1;
        if(x<=mid)  change(ls(k),l,mid,x,ls(k));
        else  change(rs(k),mid+1,r,x,rs(k));
        sum(k)=sum(ls(k))+sum(rs(k));
    }
    int query(int k,int p,int l,int r,int x){
        if(l==r){
            return l;
        }
        int mid=(l+r)>>1;
        int sum=sum(ls(k))-sum(ls(p));
        if(sum>=x)  return query(ls(k),ls(p),l,mid,x);
        else  return query(rs(k),rs(p),mid+1,r,x-sum);
    }
}tree;
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        tmp.push_back(a[i]);
    }
    sort(tmp.begin(),tmp.end());
    tmp.erase(unique(tmp.begin(),tmp.end()),tmp.end());
    len=tmp.size();
    for(int i=1;i<=n;i++){
        a[i]=lower_bound(tmp.begin(),tmp.end(),a[i])-tmp.begin()+1;
    }
    for(int i=1;i<=n;i++){
        tree.change(st[i-1],1,len,a[i],st[i]);
    }
    for(int i=1;i<=m;i++){
        int l,r,k;
        scanf("%d%d%d",&l,&r,&k);
        printf("%d\n",tmp[tree.query(st[r],st[l-1],1,len,k)-1]);
    }
}

T2:

调了好久,注意,你就把split操作要分开的的地方全都复制成新节点即可(因为你的1变化一定不能影响到原来的节点)

有的题解说merge操作也要复制原来的节点,我不敢苟同这个观点,事实证明,最后我没复制也过了

一定要注意题目中的这个条件

和原本平衡树不同的一点是,每一次的任何操作都是基于某一个历史版本,同时生成一个新的版本。

点击查看代码
#include<bits/stdc++.h>
#define ls(x) tr[x].ls
#define rs(x) tr[x].rs
#define siz(x) tr[x].siz
#define pri(x) tr[x].pri
#define key(x) tr[x].key
using namespace std;
const int N=5e5+5,inf=2147483647;
int cnt,t,n;
int root[N];
struct dot{
    int ls,rs,siz,pri,key;
    void ins(int x){
        ls=rs=0;
        key=x;
        siz=1;
        pri=rand();
    }
}tr[N<<7];
struct tree{
    void init(){
        srand(time(NULL));
    }
    int newnode(int u){
        tr[++cnt]=tr[u];
        tr[cnt].pri=rand();
        return cnt;
    }
    int makenode(int x){
        // if(x==0)  return 0;
        tr[++cnt].ins(x);
        return cnt;
    }
    void update(int u){
        siz(u)=siz(ls(u))+siz(rs(u))+1;//+1一定注意
    }
    void split(int u,int x,int &L,int &R){
        if(u==0){
           L=R=0; 
           return;
        }  
        u=newnode(u);
        if(key(u)<=x){//
            L=u;
            split(rs(u),x,rs(u),R);
        }
        else{
            R=u;
            split(ls(u),x,L,ls(u));
        }
        update(u);
    }
    int merge(int L,int R){
        if(L==0||R==0)  return L+R;
        if(pri(L)>=pri(R)){
            // L=newnode(L);
            rs(L)=merge(rs(L),R);
            update(L);
            return L;
        }
        else{
            // R=newnode(R);
            ls(R)=merge(L,ls(R));
            update(R);
            return R;
        }
    }
    void insert(int v,int x){
        int L,R;
        split(root[v],x,L,R);
        root[++t]=merge(merge(L,makenode(x)),R);
    }
    void delite(int v,int x){
        int L,R,p;
        split(root[v],x,L,R);
        split(L,x-1,L,p);
        if(p)  p=merge(ls(p),rs(p));
        root[++t]=merge(merge(L,p),R);
    }
    int kth(int u,int k){
        if(siz(ls(u))+1==k)  return u;
        if(siz(ls(u))>=k)  return kth(ls(u),k);
        return kth(rs(u),k-siz(ls(u))-1);
    }
    int pre(int v,int x){
        int L,R,res=-inf;
        split(root[v],x-1,L,R);
        if(siz(L))  res=key(kth(L,siz(L)));//
        root[v]=merge(L,R);
        return res;
    }
    int lst(int v,int x){
        int L,R,res=inf;
        split(root[v],x,L,R);
        if(siz(R))  res=key(kth(R,1));
        root[v]=merge(L,R);
        return res;
    }
    int query(int v,int x){
        int L,R;
        split(root[v],x-1,L,R);
        int res=siz(L)+1;
        root[v]=merge(L,R);
        return res;
    }
}FHQ;
int main(){
    FHQ.init();
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        int v,op,x;
        scanf("%d%d%d",&v,&op,&x);
        if(op==1){
            FHQ.insert(v,x);
        }
        else if(op==2){
            FHQ.delite(v,x);
        }
        else{
            root[++t]=root[v];//
            if(op==3){
                printf("%d\n",FHQ.query(v,x));
            }
            else if(op==4){
                printf("%d\n",key(FHQ.kth(root[v],x)));
            }
            else if(op==5){
                printf("%d\n",FHQ.pre(v,x));
            }
            else{
                printf("%d\n",FHQ.lst(v,x));
            }
        }
        
    }
}

posted @ 2025-07-31 19:30  daydreamer_zcxnb  阅读(10)  评论(0)    收藏  举报