可持久化数据结构
可持久化线段树
顾名思义,就是可以存储历史信息的线段树。
比如我们对数组进行了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;
}