可持久化数据结构

可持久化线段树

顾名思义,就是可以存储历史信息的线段树。
比如我们对数组进行了n次修改,然后突然希望回到某个第i次版本。然后又基于这个版本进行一些新的修改等,就是可持久化线段树需要解决的问题。
我们仍然考虑单点修改与区间求和:



要点在哪里呢?实际上关键在于:我们不再修改每个老节点的信息,而是类似于动态开点线段树一样,要改一个节点的时候就创建一个新的节点!
这样,老的线段树(蓝色)并不会被影响。而查询新的版本的时候,只需要从新的根进入(橙色10号点)就可以访问新的版本的线段树(10,11,12)
当然,很明显可以看到:新的线段树的节点会与之前的线段树节点有所重合!因为它们是一样的,我们不必整个复制。
那下次又修改到蓝色节点怎么办?会不会影响之前的版本?:答案当然是不会,注意我们的关键在于要改一个节点的时候就创建一个新的节点!所以老节点永远不会被改变,只会有新节点加入。
比如像下图这样:每个新的根代表一个新的版本,而每个版本都可能与之前的版本有交叉。

#include<algorithm>
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int N=1e6+10,logn=21;
int tot,n,m,a[N],root[N];
struct PST {
	int l,r,val;
} tree[N*logn];
int clone(int p) {
	tree[++tot]=tree[p];
	return tot;
}
int maketree(int p,int l,int r) {
	p=++tot;
	if(l==r) {
		tree[p].val=a[l];
		return tot;
	}
	int mid=(l+r)>>1;
	tree[p].l=maketree(tree[p].l,l,mid);
	tree[p].r=maketree(tree[p].r,mid+1,r);
	return p;
}
int modify(int p,int l,int r,int x,int val) {
	p=clone(p);
	if(l==r) tree[p].val=val;
	else {
		int mid=(l+r)/2;
		if(x<=mid) tree[p].l=modify(tree[p].l,l,mid,x,val);
		else tree[p].r=modify(tree[p].r,mid+1,r,x,val);
	}
	return p;
}
int query(int p,int l,int r,int x) {
	if(l==r) return tree[p].val;
	else {
		int mid=(l+r)/2;
		if(x<=mid) return query(tree[p].l,l,mid,x);
		else return query(tree[p].r,mid+1,r,x);
	}
}
int main() {
	scanf("%d%d",&n,&m);
	for(int i=1; i<=n; i++) scanf("%d",&a[i]);
	root[0]=maketree(0,1,n);
	for(int i=1,rt,op,x,y; i<=m; i++) {
		scanf("%d%d%d",&rt,&op,&x);
		if(op==1) {
			scanf("%d",&y);
			root[i]=modify(root[rt],1,n,x,y);
		} else {
			printf("%d\n",query(root[rt],1,n,x));
			root[i]=root[rt];
		}
	}
	return 0;
}

静态区间第 \(k\)

先说前缀第 \(k\) 小,先将数据离散化,建立一颗值域线段树,对于\(a_i=k\),则树上包含 \(k\) 的区间值+1
从1号节点开始,在线段树上二分。一个节点存储的值是其区间内数据的个数。
若1号节点左儿子的值 \(v_{ls} < k\) 那么说明第k大的值在右子树,将 \(k\) 减去\(v_{ls}\),递归右子树。
若1号节点左儿子的值 \(v_{ls} \ge k\) 那么说明第k大的值在左子树,递归左子树。
以此类推。

那么我们既然已经学会了前缀第 \(k\) 小,那么怎么处理区间第k小?
对于每个前缀,我们都建立一颗权值线段树,这里就用到可持久化线段树。
因为线段树满足可减性,我们把版本 \(r\) 的线段树减去 \(l-1\) 的线段树,即把每一个节点相减,然后求 \(k\) 大值。

#include<bits/stdc++.h>
#define int long long 
#define Maxn 200010
using namespace std;
inline void read(int &x)
{
    int f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    x*=f;
}
int n,m;
struct Segtree
{
    int ls,rs,sum;
}tree[Maxn<<5];
int rt[Maxn];
int a[Maxn],ins[Maxn]; 
int len,tot=0;
inline void Init(){tot=0;}
inline int getid(const int &x)
{
    return lower_bound(ins+1,ins+len+1,x)-ins;
}
inline void pushup(int rt)
{
    tree[rt].sum=tree[tree[rt].ls].sum+tree[tree[rt].rs].sum;
}
inline int build(int l,int r)
{
    int rt=++tot;
    if(l==r) 
    {
        tree[rt].sum=0;
        return rt;
    }
    int mid=(l+r)/2;
    tree[rt].ls=build(l,mid);
    tree[rt].rs=build(mid+1,r);
    pushup(rt);
    return rt;
}
int update(int k,int l,int r,int root,int val)
{
    int rt=++tot;
    tree[rt]=tree[root];
    if(l==k&&r==k)
    {
        tree[rt].sum+=val;
        return rt;
    }
    int mid=(l+r)/2;
    if(k<=mid) tree[rt].ls=update(k,l,mid,tree[rt].ls,val);
    else tree[rt].rs=update(k,mid+1,r,tree[rt].rs,val);
    pushup(rt);
    return rt;
}
int query(int u,int v,int l,int r,int k)
{
    if(l==r) return l;
    int mid=(l+r)/2,x=tree[tree[v].ls].sum-tree[tree[u].ls].sum;
    if(k<=x) return query(tree[u].ls,tree[v].ls,l,mid,k);
    else return query(tree[u].rs,tree[v].rs,mid+1,r,k-x);
}
signed main()
{
    Init();
    read(n),read(m);
    for(int i=1;i<=n;i++)
    {
        read(a[i]);
    }
    memcpy(ins,a,sizeof(ins));
    sort(ins+1,ins+n+1);
    len=unique(ins+1,ins+n+1)-ins-1;
    rt[0]=build(1,len);
    for(int i=1;i<=n;i++)
    {
        rt[i]=update(getid(a[i]),1,len,rt[i-1],1);
    }
    while(m--)
    {
        int l,r,k;
        read(l),read(r),read(k);
        printf("%lld\n",ins[query(rt[l-1],rt[r],1,len,k)]);
    }
    return 0;
}

若是用可持久化0-1字典树也可以

#include<algorithm>
#include<cstdio>
#include<iostream>
using namespace std;
const int N=2e5+10;
int rt[N],ch[2][32*N],num[32*N],tot,n,m;
void ins(int pre,int v,int nxt) {
	rt[nxt]=++tot;
	int nxtx=tot,prex=rt[pre];
	for(int p=1<<30; p; p>>=1) {
		bool chn=v&p;
		ch[chn][nxtx]=++tot;
		ch[!chn][nxtx]=ch[!chn][prex];
		num[ch[chn][nxtx]]=num[ch[chn][prex]]+1;
		nxtx=ch[chn][nxtx]; prex=ch[chn][prex];
	}
}
int query(int l,int r,int k) {
	int res=0,nowx=rt[r],prex=rt[l-1];
	for(int p=1<<30; p; p>>=1) {
		if(num[ch[0][nowx]]-num[ch[0][prex]]<k) {
			k-=num[ch[0][nowx]]-num[ch[0][prex]];
			nowx=ch[1][nowx]; prex=ch[1][prex];
			res+=p;
		} else {nowx=ch[0][nowx]; prex=ch[0][prex];}
	}
	return res;
}
int main() {
	scanf("%d%d",&n,&m);
	for(int i=1,x; i<=n; i++) {
		scanf("%d",&x);
		ins(i-1,x,i);
	}
	for(int i=1,l,r,k; i<=m; i++) {
		scanf("%d%d%d",&l,&r,&k);
		printf("%d\n",query(l,r,k));
	}
	return 0;
}

P3293 [SCOI2016]美味

若要找异或的最大值,即借鉴一下最大异或和的解题思想即可。
那么现在就是要找所有 \(a_i+x\) xor \(b_i\)最大。
我们把 \(b_i\) 转为二进制,从头开始枚举,对0找1,对1找0。
譬如 \(1011\),现在找第一位,那么就是在 \([l,r]\) 中找有没有 \(a_i+x\)\([0000,0111]\) 中.
则找 \(a_i\)\([0000-x,0111-x]\) 中.
若有,则答案这一位为 \(1\), 下一位找 \([0100-x,0111-x]\).
否则为 \(0\) ,下一位找 \([1100-x,1111-x]\).
以此类推。

#include<cstdio>
#include<algorithm>
using namespace std;
const int N=5e5+5;
const int M=N*20;
int a[N],root[N];
int n,m,totn=0,T_cnt=1;
struct President_Tree{
    int L,R,sum;
}T[M];
int read(){
    int x=0,f=1;char ch=getchar();
    while (ch<'0' || ch>'9'){if (ch=='-')f=-1;ch=getchar();}
    while ('0'<=ch && ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
    return x*f;
}
void insert(int &now,int index,int l=0,int r=totn){
    T[T_cnt++]=T[now];now=T_cnt-1;
    T[now].sum++;
    if (l==r)return;
    int mid=(l+r)>>1;
    if (index<=mid)insert(T[now].L,index,l,mid);
        else insert(T[now].R,index,mid+1,r);
}
int query(int i,int j,int ql,int qr,int l=0,int r=totn){
    if (ql<=l && r<=qr)return T[j].sum-T[i].sum;
    int mid=(l+r)>>1,tt=0;
    if (ql<=mid)tt+=query(T[i].L,T[j].L,ql,qr,l,mid);
    if (mid<qr)tt+=query(T[i].R,T[j].R,ql,qr,mid+1,r);
    return tt;
}
bool find(int i,int j,int ql,int qr){
    ql=max(0,ql);qr=min(qr,totn);
    if (ql>qr)return 0;
    return query(root[i],root[j],ql,qr);
}
int main(){
    n=read(),m=read();
    for (int i=1;i<=n;i++)
        totn=max(totn,a[i]=read());
    root[0]=0;
    for (int i=1;i<=n;i++){
        root[i]=root[i-1];
        insert(root[i],a[i]);
    }
    for (int i=1;i<=m;i++){
        int b=read(),x=read(),ql=read(),qr=read(),ans=0;
        for (int i=17;i>=0;i--){
            int now=ans+((1^((b>>i)&1))<<i);
            if (find(ql-1,qr,now-x,now+(1<<i)-1-x))ans=now;
            else ans+=((b>>i)&1)<<i;
        }
        printf("%d\n",ans^b);
    }
    return 0;
}
posted @ 2022-08-13 15:18  s1monG  阅读(33)  评论(0编辑  收藏  举报