可持久化前提:
本身的拓扑结构不改变 (平衡树左右旋,拓扑结构改变,不可持久化)
核心思想:
存下数据结构的所有历史版本时,只记录每个版本与前一个版本 改变的部分,每次新增部分至多log(n)个节点,m次为mlog(n)
可持久化trie:
算法步骤:
1、插入:枚举str的每一位,当前位s[i]新建一个节点,当前位的其余字符都与上一版本相同,直接copy
2、查询:查询(l,r)的某个信息时,利用历史版本的可减性,( r ) - ( l-1 )得到区间(l,r)的信息
P4735 最大异或和
题意:在前缀和{S[i]}中,找到一个 S[pos],求与常数C的异或最大值
限制 L <= pos <= R
如果只限制(1,R),直接可持久化trie,记录历史版本即可
如果再增加下标的下界限制:
法一:预处理trie中每个子树的mxid (可递归实现)
法二:预处理trie中每个子树中 数的个数cnt,再利用可持久化trie的可减性得到区间(l,r)的个数,每次贪心找 当前位相反且个数不为0的数
代码:
const int N=6e5+10;
int pre[N];
int tr[N*25][2], cnt[N*25];//0~23, 24位,24条边,25个点
int root[N],idx;
/*
void insert(int q,int p,int i,int k){
if(k<0){ cnt[q]=cnt[p] +1; return ; }//初态,注意要先copy上一版本p的cnt,再+1
int v=(pre[i]>>k)&1;
if(p) tr[q][v^1]=tr[p][v^1];//copy
tr[q][v]=++idx;
insert(tr[q][v],tr[p][v], i,k-1);
cnt[q]=cnt[tr[q][0]] + cnt[tr[q][1]]; //计算trie子树中 数字个数
}*/
void insert(int q,int p,int val){
fd(i,23,0){
int x=(val>>i)&1;
tr[q][x^1]=tr[p][x^1];//
cnt[q]=cnt[p];//copy
cnt[q]+=1;
tr[q][x]=++idx;
q=tr[q][x], p=tr[p][x];
}
cnt[q]=cnt[p] +1;//特别注意:最后的叶子也要更新cnt
}
int query(int q,int p, int C){
int res=0;
fd(i,23,0){
int v=(C>>i)&1;
if( tr[q][v^1] && cnt[tr[q][v^1]] - cnt[tr[p][v^1]]>0 ) q=tr[q][v^1], p=tr[p][v^1], res|=(1<<i);
else q=tr[q][v], p=tr[p][v];
}
return res;
}
int Work(){
pre[0]=0;
int n,q; scanf("%d%d",&n,&q);
fu(i,1,n) {
int x; scanf("%d",&x);
pre[i]=pre[i-1]^x;
}
// root[0]=++idx; insert(root[0],0, 0,23);
// fu(i,1,n) root[i]=++idx, insert(root[i],root[i-1], i,23);
root[0]=++idx, insert(root[0],0, pre[0]);
fu(i,1,n) root[i]=++idx, insert(root[i],root[i-1], pre[i]);
while(q--){
char op[2]; scanf("%s",op);
if(op[0]=='A'){
int x; scanf("%d",&x);
++n; pre[n]=pre[n-1]^x;
// root[n]=++idx, insert(root[n],root[n-1], n,23);
root[n]=++idx, insert(root[n],root[n-1], pre[n]);
}
else {
int l,r,x; scanf("%d%d%d",&l,&r,&x);
if(l-2<0) printf("%d\n",query(root[r-1],0, pre[n]^x) );
else printf("%d\n",query(root[r-1],root[l-2], pre[n]^x) );//防越界, p-1 (l-1,r-1)
}
}
return 0;
}
可持久化线段树 / 主席树
解决区间(l,r)中 介于(x,y)的问题
算法步骤:
1、 建树,动态开点
2、 插入:与可持久化trie类似,相同的直接copy,只需建立新增部分
3、 查询:利用可减性计算区间(l,r)的信息
P3834 【模板】可持久化线段树 2(主席树)
题意:求区间(l,r) 第k小数
代码:
const int N=2e5+10;
int a[N],A[N],tot;
struct node{
int l,r; int cnt;
}t[N*4 +N*18];// m次修改,空间加 mlog(n)
int root[N],idx;
void up(int q){ t[q].cnt=t[t[q].l].cnt + t[t[q].r].cnt; }
int build(int l,int r){//新建区间(l,r)的节点q
int q=++idx;
if(l==r) return q; //叶子
int mid=(l+r)>>1;
t[q].l=build(l,mid), t[q].r=build(mid+1,r);
return q;
}
int insert(int p,int l,int r, int val){//在区间(l,r)找val的位置,插入val (离散化后,pos=val)
int q=++idx;
t[q]=t[p];//完全copy
if(l==r){ t[q].cnt+=1; return q; }
int mid=(l+r)>>1;
if(val<=mid) t[q].l=insert(t[p].l, l,mid,val);
else t[q].r=insert(t[p].r, mid+1,r,val);
up(q);
return q;//返回当前插入后的 新版本q
}
int query(int q,int p,int l,int r,int k){
if(l==r) return l;
int cnt=t[t[q].l].cnt - t[t[p].l].cnt;//root[r]版本的左子树中 下标在(l,r)的个数
int mid=(l+r)>>1;
if(k<=cnt) return query(t[q].l,t[p].l, l,mid,k);
else return query(t[q].r,t[p].r, mid+1,r,k-cnt);
}
int getid(int val){
return lower_bound(A+1,A+1+tot,val) -A;
}
int Work(){
tot=idx=0;
int n,q; cin>>n>>q;
fu(i,1,n) scanf("%d",a+i), A[++tot]=a[i];
sort(A+1,A+1+tot);
tot=unique(A+1,A+1+tot)-(A+1);
root[0]=build(1,tot); //初始建树
fu(i,1,n) root[i]=insert(root[i-1],1,tot, getid(a[i]));//每次修改插入一个数,返回新的根节点q
while(q--){
int l,r,k; scanf("%d%d%d",&l,&r,&k);
int id=query(root[r],root[l-1],1,tot,k);
printf("%d\n",A[id]);
}
return 0;
}