可持久化伴性遗传(更新中)
1.可持久化线段树
板1
注意:节点的左右儿子编号不再是节点编号\(*2\)或\(*2+1\),所以我们要开一个结构体存每个节点的左右儿子。注意空间开大点。
点击查看
#include<bits/stdc++.h>
#define l(i) tr[i].l
#define r(i) tr[i].r
#define pr(i) tr[i].pre
#define mid ((l+r)>>1)
using namespace std;
const int N=1e6+10;
int a[N*10],rt[N*10],cnt,n,m;
struct tree{
int l,r,pre;
}tr[N*30];
inline int read()
{
int s=0,x=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')x=-1;ch=getchar();}
while(isdigit(ch))s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
return s*x;
}
inline int copy(int old)
{
tr[++cnt]=tr[old];
return cnt;
}
inline int build(int i,int l,int r)
{
i=++cnt;
if(l==r)
{
pr(i)=a[l];
return i;
}
l(i)=build(l(i),l,mid);
r(i)=build(r(i),mid+1,r);
return i;
}
inline int update(int i,int l,int r,int x,int k)
{
i=copy(i);
if(l==r)
{
pr(i)=k;
return i;
}
if(x<=mid)l(i)=update(l(i),l,mid,x,k);
else r(i)=update(r(i),mid+1,r,x,k);
return i;
}
int getsum(int i,int l,int r,int x)
{
if(l==r)return pr(i);
if(x<=mid)return getsum(l(i),l,mid,x);
else return getsum(r(i),mid+1,r,x);
}
int main()
{
n=read(),m=read();
for(register int i=1;i<=n;++i)a[i]=read();
rt[0]=build(0,1,n);
for(register int i=1;i<=m;++i)
{
int v=read(),opt=read(),loc=read();
if(opt==1)
{
int val=read();
rt[i]=update(rt[v],1,n,loc,val);
}
else
{
printf("%d\n",getsum(rt[v],1,n,loc));
rt[i]=rt[v];
}
}
return 0;
}
我们在原序列中的每个位置都开一棵线段树,第\(i\)棵线段树上的区间\([x,y]\)表示\(1\)~\(i\)中在\([x,y]\)中数的个数。由于原题的值域范围很大,所以我们需要进行离散化。其他地方就和板1一样了。
点击查看
#include<bits/stdc++.h>
#define siz(i) tr[i].siz
#define l(i) tr[i].l
#define r(i) tr[i].r
#define mid (l+r>>1)
using namespace std;
const int N=2e5+10;
inline int read()
{
int s=0;char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch))s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
return s;
}
int n,q,a[N],b[N],rt[N],m;
int cnt;
struct node{
int siz,l,r;
}tr[N<<5];
inline int copy(int old)
{
tr[++cnt]=tr[old];
return cnt;
}
inline int build(int i,int l,int r)
{
i=++cnt;
if(l==r)return i;
l(i)=build(l(i),l,mid);
r(i)=build(r(i),mid+1,r);
siz(i)=siz(l(i))+siz(r(i));
return i;
}
inline int update(int i,int l,int r,int x)
{
i=copy(i);
if(l==r)
{
siz(i)++;
return i;
}
if(x<=mid)l(i)=update(l(i),l,mid,x);
else r(i)=update(r(i),mid+1,r,x);
siz(i)=siz(l(i))+siz(r(i));
return i;
}
int getsum(int x,int y,int l,int r,int k)
{
if(l==r)return l;
int siz=siz(l(y))-siz(l(x));
if(siz>=k)return getsum(l(x),l(y),l,mid,k);
else return getsum(r(x),r(y),mid+1,r,k-siz);
}
int main()
{
n=read();q=read();
for(register int i=1;i<=n;++i)b[i]=a[i]=read();
sort(b+1,b+n+1);
m=unique(b+1,b+n+1)-b-1;
for(register int i=1;i<=n;++i)a[i]=lower_bound(b+1,b+m+1,a[i])-b;
rt[0]=build(0,1,n);
for(register int i=1;i<=n;++i)rt[i]=update(rt[i-1],1,n,a[i]);
while(q--)
{
int l=read(),r=read(),k=read();
printf("%d\n",b[getsum(rt[l-1],rt[r],1,n,k)]);
}
return 0;
}
2.可持久化平衡树
先来看fhq。我们观察它的几个基本操作,发现会对可持久化造成影响的只有merge(合并)和split(分裂)两个操作。
我们用root[]数组存下每个版本的根节点,对于它的可持久化操作,我们惊人地发现:它和主席树的思想没什么区别。
记得空间开大点。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=5e5+10;
int n,son[N*50][2],siz[N*50],bal[N*50];
int cnt,root[N];
ll val[N*50];
void copy(int x,int y)
{
son[x][1]=son[y][1];son[x][0]=son[y][0];
siz[x]=siz[y];bal[x]=bal[y];val[x]=val[y];
}
void update(int x){siz[x]=(son[x][0]?siz[son[x][0]]:0)+(son[x][1]?siz[son[x][1]]:0)+1;}
int build(ll w=0)
{
siz[++cnt]=1;
val[cnt]=w;bal[cnt]=rand();
return cnt;
}
int merge(int x,int y)
{
if(!x||!y)return x|y;
int rt=build();
if(bal[x]<bal[y])
{
copy(rt,x);
son[rt][1]=merge(son[rt][1],y);
update(rt);
return rt;
}
copy(rt,y);
son[rt][0]=merge(x,son[rt][0]);
update(rt);
return rt;
}
void split(int rt,ll k,int &x,int &y)
{
if(!rt)x=y=0;
else
{
if(val[rt]<=k)
{
x=build();
copy(x,rt);
split(son[x][1],k,son[x][1],y);
update(x);
}
else
{
y=build();
copy(y,rt);
split(son[y][0],k,x,son[y][0]);
update(y);
}
}
}
int find(int rt,ll k)
{
while(1)
{
if(siz[son[rt][0]]>=k)rt=son[rt][0];
else
{
if(son[rt][0])k-=siz[son[rt][0]];
if(!--k)return rt;
rt=son[rt][1];
}
}
}
int tr1,tr2,tr3;
int main()
{
srand(time(0));
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
tr1=tr2=tr3=0;
int v,opt;ll x;scanf("%d %d %lld",&v,&opt,&x);
root[i]=root[v];
if(opt==1)
{
split(root[i],x,tr1,tr2);
root[i]=merge(merge(tr1,build(x)),tr2);
}
else if(opt==2)
{
split(root[i],x,tr1,tr3);
split(tr1,x-1,tr1,tr2);
tr2=merge(son[tr2][0],son[tr2][1]);
root[i]=merge(merge(tr1,tr2),tr3);
}
else if(opt==3)
{
split(root[i],x-1,tr1,tr2);
printf("%d\n",siz[tr1]+1);
root[i]=merge(tr1,tr2);
}
else if(opt==4)printf("%lld\n",val[find(root[i],x)]);
else if(opt==5)
{
split(root[i],x-1,tr1,tr2);
if(tr1==0)
{
puts("-2147483647");
continue;
}
printf("%lld\n",val[find(tr1,siz[tr1])]);
root[i]=merge(tr1,tr2);
}
else if(opt==6)
{
split(root[i],x,tr1,tr2);
if(tr2==0)
{
puts("2147483647");
continue;
}
printf("%lld\n",val[find(tr2,1)]);
root[i]=merge(tr1,tr2);
}
}
return 0;
}
对于splay,我们发现它太麻烦了,所以直接跳过
3.可持久化文艺平衡树
首先,还是用fhq。常数比splay小,实现比splay简单,思路比splay好像,傻子才用splay
操作:
1.插入:把平衡树在位置p处分裂,然后把x和分裂出来的两棵平衡树合并。
2.删除:把平衡树分裂为[1,p-1],p,[p+1,n]三课树,然后将左右两棵树合并。
3.翻转:把区间[l,r]拆下来,然后打上标记再安回去。
4.求和:提前用一个数组sum存平衡树上的区间和,求和时,把区间[l,r]拆下来,求和,输出。
注意:分裂/合并时要先推懒标记再分裂/合并。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define l(p) tr[p].l
#define r(p) tr[p].r
#define val(p) tr[p].val
#define lazy(p) tr[p].lazy
#define rnd(p) tr[p].rnd
#define siz(p) tr[p].siz
#define sum(p) tr[p].sum
using namespace std;
const int N=2e5+10;
struct fhq{
ll val,sum;
int l,r,lazy,rnd,siz;
}tr[N<<7];
int rt[N],cnt;
ll lastans;
inline int newnode(ll w=0)
{
val(++cnt)=w;sum(cnt)=w;
rnd(cnt)=rand();siz(cnt)=1;
return cnt;
}
inline int copy(int old)
{
int nw=newnode();
tr[nw]=tr[old];
return nw;
}
void pushdown(int p)
{
if(!lazy(p))return ;
if(l(p))l(p)=copy(l(p));
if(r(p))r(p)=copy(r(p));
swap(l(p),r(p));
if(l(p))lazy(l(p))^=1;
if(r(p))lazy(r(p))^=1;
lazy(p)=0;
}
void pushup(int p)
{
siz(p)=siz(l(p))+siz(r(p))+1;
sum(p)=sum(l(p))+sum(r(p))+val(p);
}
inline void split(int p,int k,int &x,int &y)
{
if(!p)
{
x=y=0;
return ;
}
pushdown(p);
if(siz(l(p))<k)
{
x=copy(p);
split(r(x),k-siz(l(p))-1,r(x),y);
pushup(x);
}
else
{
y=copy(p);
split(l(y),k,x,l(y));
pushup(y);
}
}
int merge(int x,int y)
{
if(!x||!y)return x|y;
pushdown(x);pushdown(y);
if(rnd(x)<rnd(y))
{
r(x)=merge(r(x),y);
pushup(x);
return x;
}
l(y)=merge(x,l(y));
pushup(y);
return y;
}
inline int read()
{
int s=0,x=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')x=-1;ch=getchar();}
while(isdigit(ch))s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
return s*x;
}
int x,y,z;
signed main()
{
int n=read();
for(int i=1;i<=n;++i)
{
x=y=z=0;
int v=read(),opt=read();
switch(opt)
{
case 1:{
int p=read()^lastans,a=read()^lastans;
split(rt[v],p,x,y);
rt[i]=merge(merge(x,newnode(a)),y);
break;
}
case 2:{
int p=read()^lastans;
split(rt[v],p,x,y);
split(x,p-1,x,z);
rt[i]=merge(x,y);
break;
}
case 3:{
int a=read()^lastans,b=read()^lastans;
split(rt[v],b,x,y);
split(x,a-1,x,z);
lazy(z)^=1;
rt[i]=merge(merge(x,z),y);
break;
}
case 4:{
int a=read()^lastans,b=read()^lastans;
split(rt[v],b,x,y);
split(x,a-1,x,z);
printf("%lld\n",lastans=sum(z));
rt[i]=merge(merge(x,z),y);
break;
}
}
}
return 0;
}
3.可持久化trie
鸽
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=6e5+10;
int idx,tr[N<<5][3],root[N],last[N<<5];
int l,r,val,a,b,c,sum[N],n,m;
inline void insert(int pre,int now,int i,int k)
//上一个版本根位置,当前版本根位置,插入序号,插入到哪一位
{
if(k<0)
{
last[now]=i;//更新最晚插入版本
return;
}
bool id=(sum[i]>>k)&1;//寻找插入字符
if(pre)tr[now][id^1]=tr[pre][id^1];//更新当前版本
tr[now][id]=++idx;//新建节点
insert(tr[pre][id],tr[now][id],i,k-1);
last[now]=max(last[tr[now][0]],last[tr[now][1]]);//更新最晚插入版本
}
int find(int l,int now,int val)
//左边界,右边界对应根的位置,查询值
{
int p=now;
for(int i=23;i>=0;--i)
{
bool id=(val>>i)&1;
if(last[tr[p][id^1]]>=l)p=tr[p][id^1];
else p=tr[p][id];
}
return sum[last[p]]^val;
}
inline int read()
{
int s=0;char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch))s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
return s;
}
int main()
{
n=read(),m=read();
root[0]=++idx;
last[0]=-1;
insert(0,root[0],0,23);
for(int i=1;i<=n;++i)
{
a=read();
sum[i]=sum[i-1]^a;
root[i]=++idx;
insert(root[i-1],root[i],i,23);
}
while(m--)
{
char op;cin>>op;
if(op=='A')
{
a=read();
n++;
sum[n]=sum[n-1]^a;
root[n]=++idx;
insert(root[n-1],root[n],n,23);
}
else
{
a=read(),b=read(),c=read();
printf("%d\n",find(a-1,root[b-1],sum[n]^c));
}
}
return 0;
}
拓:基于主席树实现的可持久化并查集
与普通并查集相比,唯一多出来的就是回退操作。容易看出,两个版本之间根本差别就在于fa数组。
我们尝试对fa数组可持久化。具体的,我们建一棵主席树,树上的叶子代表编号为x的父亲。
之前我们对于并查集之间的合并采用的优化方法是路径压缩,但在可持久化中,我们无法再用路径压缩优化。
原因:
路径压缩的复杂度是均摊的,但是均摊在可持久化中是万万不可接受的,因为可能存在某一次的复杂度是\(n^2\)的,而在我们的操作中有返回之前的版本,万恶的出题人可能会找到一次复杂度\(n^2\)的操作,然后让你不停地回退,重复操作,这样你就会被卡似。
so,我们需要一种单次严格\(logn\)的合并方法,这时候,按秩合并就出现了。
按秩合并有三种方式:
1.深度
2.大小
3.随机
这里只讲第一个第二个不会,第三个被卡掉了。
按照深度合并的话,我们需要记录节点的\(fa\)和深度\(dep\)。
对于一次操作合并\(x\),\(y\)。
我们让\(x=find(x)\),\(y=find(y)\)。
(假定\(dep_y\)\(\le\)\(dep_x\))显然是把\(y\)合并到\(x\)的下面,这样才能使我们的深度更小。
考虑合并对深度的影响:
- 若\(dep_x\)\(>\)\(dep_y\),则深度不变。
- 若\(dep_x\)\(=\)\(dep_y\),则\(dep_x+1\)。
这样合并,单次复杂度是严格\(logn\)的,全部合并完,树最高也只有\(logn\)
具体的,对于一次修改,我们要新建两个版本。
把这个版本中\(fa_y\)改成\(x\),然后修改\(dep_x\)。
点击查看代码
#include<bits/stdc++.h>
#define fa(x) tr[x].fa
#define l(x) tr[x].l
#define r(x) tr[x].r
#define dep(x) tr[x].dep
#define mid (l+r>>1)
using namespace std;
const int N=1e5+10;
int rt[N<<5],a[N<<5],n,m,cnt;
struct node{
int fa,l,r,dep;
}tr[N<<5];
int copy(int x){tr[++cnt]=tr[x];return cnt;}
int build(int i,int l,int r)
{
i=++cnt;
if(l==r)
{
dep(i)=1;fa(i)=l;
return i;
}
l(i)=build(l(i),l,mid);
r(i)=build(r(i),mid+1,r);
return i;
}
int findid(int i,int l,int r,int x)
{
if(l==r)return i;
if(x<=mid)return findid(l(i),l,mid,x);
return findid(r(i),mid+1,r,x);
}
int find(int i,int idx,int b)
{
if(fa(idx)==i)return i;
return find(fa(idx),findid(b,1,n,fa(idx)),b);
}
int merge(int i,int l,int r,int x,int y)
{
i=copy(i);
if(l==r)
{
fa(i)=y;
return i;
}
if(x<=mid)l(i)=merge(l(i),l,mid,x,y);
else r(i)=merge(r(i),mid+1,r,x,y);
return i;
}
int add(int i,int l,int r,int x)
{
i=copy(i);
if(l==r)
{
dep(i)++;
return i;
}
if(x<=mid)l(i)=add(l(i),l,mid,x);
else r(i)=add(r(i),mid+1,r,x);
return i;
}
void solve(int i,int x,int y)
{
rt[i]=rt[i-1];
int Fa=find(x,findid(rt[i],1,n,x),rt[i]),Fb=find(y,findid(rt[i],1,n,y),rt[i]);
if(Fa==Fb)return ;
int ida=findid(rt[i],1,n,Fa),idb=findid(rt[i],1,n,Fb);
if(dep(ida)>dep(idb))swap(Fa,Fb);
rt[i]=merge(rt[i],1,n,Fa,Fb);
if(dep(ida)==dep(idb))rt[i]=add(rt[i],1,n,Fb);
}
int main()
{
scanf("%d %d",&n,&m);
rt[0]=build(0,1,n);
for(int i=1;i<=m;++i)
{
int opt;scanf("%d",&opt);
switch(opt)
{
case 1:{
int a,b;scanf("%d %d",&a,&b);
solve(i,a,b);
break;
}
case 2:{
int k;scanf("%d",&k);
rt[i]=rt[k];
break;
}
case 3:{
int a,b;scanf("%d %d",&a,&b);
rt[i]=rt[i-1];
int Fa=find(a,findid(rt[i],1,n,a),rt[i]),Fb=find(b,findid(rt[i],1,n,b),rt[i]);
Fa==Fb?puts("1"):puts("0");
break;
}
}
}
return 0;
}

可持久化数据结构
浙公网安备 33010602011771号