1.可持久化线段树(可持久化数组)
https://www.luogu.org/problemnew/show/P3919#sub
最基础的可持久化数据结构,每次修改开新的log个点即可。
#include
using namespace std;
const int N=1e6+100;
template
void rd(T &x)
{
char c=getchar();x=0;bool f=0;
while(!isdigit(c))f|=(c=='-'),c=getchar();
while(isdigit(c))x=x*10+c-48,c=getchar();
if(f)x=-x;
}
template
void print(T x)
{
static int tax[50],tnum;
tnum=0;
if(x<0)putchar('-'),x*=-1;
while(x)tax[++tnum]=x%10,x/=10;
for(int i=tnum;i>=1;i--)putchar('0'+tax[i]%10);
puts("");
}
struct Seg{
int ls,rs,w;
}seg[N*20];
int tot,edit[N];
int n,m,a[N],num=0;
int build(int l,int r)
{
int nw=++tot;
if(l==r)
{
seg[nw].w=a[l];
return nw;
}
int mid=(l+r)>>1;
seg[nw].ls=build(l,mid);
seg[nw].rs=build(mid+1,r);
return nw;
}
int cg(int bf,int to,int l,int r,int w)
{
int nw=++tot;
seg[nw]=seg[bf];
if(l==r)
{
seg[nw].w=w;
return nw;
}
int mid=(l+r)>>1;
if(to<=mid)seg[nw].ls=cg(seg[bf].ls,to,l,mid,w);
else seg[nw].rs=cg(seg[bf].rs,to,mid+1,r,w);
return nw;
}
int qry(int nw,int to,int l,int r)
{
if(l==r)return seg[nw].w;
int mid=(l+r)>>1;
if(to<=mid)return qry(seg[nw].ls,to,l,mid);
else return qry(seg[nw].rs,to,mid+1,r);
}
int main()
{
rd(n),rd(m);
for(int i=1;i<=n;i++)
rd(a[i]);
edit[0]=build(1,n);
int wh,op,ps,val;
while(m--)
{
rd(wh),rd(op);
if(op==1)
{
rd(ps),rd(val);
edit[++num]=cg(edit[wh],ps,1,n,val);
}
else
{
rd(ps);
edit[++num]=edit[wh];
print(qry(edit[wh],ps,1,n));
}
}
}
静态区间第k小,很久以前写的也贴在这了
#pragma GCC optimize(3,"inline","Ofast")
#include
using namespace std;
const int N=2e5;
int a[N+5],b[N+5],ls[N*20],rs[N*20],rt[N+5],seg[N*20],tot=0;
void read(int &x)
{
char c=getchar();x=0;
while(!isdigit(c))c=getchar();
while(isdigit(c))x=(x<<3)+(x<<1)+c-48,c=getchar();
}
int build(int l,int r)
{
int nw=++tot,mid=(l+r)>>1;
if(l>=r)return nw;
ls[nw]=build(l,mid);
rs[nw]=build(mid+1,r);
return nw;
}
int add(int bf,int l,int r,int x)
{
int nw=++tot,mid=(l+r)>>1;
ls[nw]=ls[bf],rs[nw]=rs[bf],seg[nw]=seg[bf]+1;
if(l>=r)return nw;
if(x<=mid)ls[nw]=add(ls[bf],l,mid,x);
else rs[nw]=add(rs[bf],mid+1,r,x);
return nw;
}
int query(int a,int b,int l,int r,int k)
{
if(l==r)return l;
int nw=seg[ls[b]]-seg[ls[a]],mid=(l+r)>>1;
if(nw>=k)return query(ls[a],ls[b],l,mid,k);
else return query(rs[a],rs[b],mid+1,r,k-nw);
}
int main()
{
int n,m,len,aa,bb,k;
read(n),read(m);
for(int i=1;i<=n;i++)
read(a[i]),b[i]=a[i];
sort(b+1,b+n+1);
len=unique(b+1,b+n+1)-b-1;
rt[0]=build(1,len);
for(int i=1;i<=n;i++){
a[i]=lower_bound(b+1,b+len+1,a[i])-b;
rt[i]=add(rt[i-1],1,len,a[i]);
}
for(int i=1;i<=m;i++){
read(aa),read(bb),read(k);
printf("%d\n",b[query(rt[aa-1],rt[bb],1,len,k)]);
}
return 0;
}
2.可持久化平衡树(fhq treap)
模板题
链接:https://www.luogu.org/problemnew/show/P3835
基本和之前线段树一样,mg和split发生改变的点都开成新点即可。个人觉得为了减少空间消耗可以分别写开新点和不开新点的mg/split,但懒得写......
#include
using namespace std;
const int N=5e5+100;
const int inf=INT_MAX;
template
void rd(T &x)
{
char c=getchar();x=0;bool f=0;
while(!isdigit(c))f|=(c=='-'),c=getchar();
while(isdigit(c))x=x*10+c-48,c=getchar();
if(f)x=-x;
}
struct gg{
int rnd,ls,rs,sz,w;
}nd[N*60];
int n,edit[N],tot=0;
void push_up(int x)
{nd[x].sz=nd[nd[x].ls].sz+nd[nd[x].rs].sz+1;}
int mg(int x,int y)
{
if(!x||!y)return x+y;
int nw=++tot;
if(nd[x].rnd<=nd[y].rnd)
{
nd[nw]=nd[x];
nd[nw].rs=mg(nd[nw].rs,y);
push_up(nw);
return nw;
}
else
{
nd[nw]=nd[y];
nd[nw].ls=mg(x,nd[nw].ls);
push_up(nw);
return nw;
}
}
void split1(int nw,int k,int &x,int &y)
{
if(!nw)x=y=0;
else
{
int p=++tot;
nd[p]=nd[nw];
if(nd[nd[p].ls].sz>=k)y=p,split1(nd[p].ls,k,x,nd[p].ls);
else x=p,split1(nd[p].rs,k-nd[nd[p].ls].sz-1,nd[p].rs,y);
push_up(p);
}
}
void split2(int nw,int k,int &x,int &y)
{
if(!nw)x=y=0;
else
{
int p=++tot;
nd[p]=nd[nw];
if(nd[p].w<=k)x=p,split2(nd[p].rs,k,nd[p].rs,y);
else y=p,split2(nd[p].ls,k,x,nd[p].ls);
push_up(p);
}
}
void P(int x)
{
if(nd[x].ls)P(nd[x].ls);
printf("%d ",nd[x].w);
if(nd[x].rs)P(nd[x].rs);
}
int new_node(int x)
{
int nw=++tot;
nd[nw].w=x,nd[nw].sz=1,nd[nw].ls=nd[nw].rs=0;
nd[nw].rnd=rand();
return nw;
}
void ins(int val,int id,int nw)
{
int x,y;
split2(edit[id],val,x,y);
edit[nw]=mg(mg(x,new_node(val)),y);
}
void del(int val,int id,int nw)
{
int x,y,z;
split2(edit[id],val-1,x,y);
split1(y,1,y,z);
if(nd[y].w!=val)edit[nw]=edit[id];
else edit[nw]=mg(x,z);
}
void rnk(int val,int id,int nw)
{
int x,y;
edit[nw]=edit[id];
split2(edit[id],val-1,x,y);
printf("%d\n",nd[x].sz);
}
void kth(int k,int id,int nw)
{
int x,y,z;
edit[nw]=edit[id];
split1(edit[id],k-1,x,y);
split1(y,1,y,z);
printf("%d\n",nd[y].w);
}
void pre(int val,int id,int nw)
{
int x,y,z;
edit[nw]=edit[id];
split2(edit[id],val-1,x,y);
split1(x,nd[x].sz-1,x,z);
printf("%d\n",nd[z].w);
}
void nxt(int val,int id,int nw)
{
int x,y,z;
edit[nw]=edit[id];
split2(edit[id],val,x,y);
split1(y,1,y,z);
printf("%d\n",nd[y].w);
}
int main()
{
rd(n);
ins(inf,0,0),ins(-inf,0,0);
int id,op,x;
for(int i=1;i<=n;i++)
{
rd(id),rd(op),rd(x);
if(op==1)ins(x,id,i);
if(op==2)del(x,id,i);
if(op==3)rnk(x,id,i);
if(op==4)kth(x+1,id,i);
if(op==5)pre(x,id,i);
if(op==6)nxt(x,id,i);
}
}
可持久化文艺平衡树
翻转也是一样的,注意每次push_down也要开新点,不然会影响历史版本,而这里按我之前那种较标准的翻转写法(见LCT篇)会比较翔而且空间常数可能还要×2,因此这里用了最简单的翻转写法(即当前点有翻转标记但是其实并未翻转)
ps:这种简单的翻转写法可能在一些题里要多push_down几次保证当前点的左右孩子顺序是实际的顺序,原来的写法则不用,因此个人在没有特殊要求下更倾向于写原来的那种
#include
#define ll long long
using namespace std;
const int N=2e5+100;
const int M=3e7+100;
template
void rd(T &x)
{
char c=getchar();x=0;bool f=0;
while(!isdigit(c))f|=(c=='-'),c=getchar();
while(isdigit(c))x=x*10+c-48,c=getchar();
if(f)x=-x;
}
struct gg{
int rnd,ls,rs,sz,w;
bool rev;ll sum;
}nd[M];
int n,edit[N],tot=0;ll las_ans=0;
void push_up(int x)
{
nd[x].sz=nd[nd[x].ls].sz+nd[nd[x].rs].sz+1;
nd[x].sum=nd[nd[x].ls].sum+nd[nd[x].rs].sum+nd[x].w;
}
void push_down(int x)
{
if(nd[x].rev)
{
int nw;
if(nd[x].ls)nw=++tot,nd[nw]=nd[nd[x].ls],nd[x].ls=nw;
if(nd[x].rs)nw=++tot,nd[nw]=nd[nd[x].rs],nd[x].rs=nw;
swap(nd[x].ls,nd[x].rs);
if(nd[x].ls)nd[nd[x].ls].rev^=1;
if(nd[x].rs)nd[nd[x].rs].rev^=1;
nd[x].rev=0;
}
}
int mg(int x,int y)
{
if(!x||!y)return x+y;
push_down(x),push_down(y);
int nw=++tot;
if(nd[x].rnd<=nd[y].rnd)
{
nd[nw]=nd[x];
nd[nw].rs=mg(nd[nw].rs,y);
push_up(nw);
return nw;
}
else
{
nd[nw]=nd[y];
nd[nw].ls=mg(x,nd[nw].ls);
push_up(nw);
return nw;
}
}
void split1(int nw,int k,int &x,int &y)
{
if(!nw)x=y=0;
else
{
int p=++tot;
push_down(nw);
nd[p]=nd[nw];
if(nd[nd[p].ls].sz>=k)y=p,split1(nd[p].ls,k,x,nd[p].ls);
else x=p,split1(nd[p].rs,k-nd[nd[p].ls].sz-1,nd[p].rs,y);
push_up(p);
}
}
int new_node(int x)
{
int nw=++tot;
nd[nw].w=x,nd[nw].sz=1,nd[nw].ls=nd[nw].rs=0;
nd[nw].rnd=rand(),nd[nw].sum=x,nd[nw].rev=0;
return nw;
}
void ins(int ps,int val,int id,int nw)
{
int x,y;
split1(edit[id],ps,x,y);
edit[nw]=mg(mg(x,new_node(val)),y);
}
void del(int ps,int id,int nw)
{
int x,y,z;
split1(edit[id],ps-1,x,y);
split1(y,1,y,z);
edit[nw]=mg(x,z);
}
void reverse(int l,int r,int id,int nw)
{
int x,y,z;
split1(edit[id],l-1,x,y);
split1(y,r-l+1,y,z),nd[y].rev^=1;
edit[nw]=mg(mg(x,y),z);
}
void qry(int l,int r,int id,int nw)
{
int x,y,z;
edit[nw]=edit[id];
split1(edit[nw],l-1,x,y);
split1(y,r-l+1,y,z);
printf("%lld\n",las_ans=nd[y].sum);
}
int main()
{
rd(n);
int id,op;ll l,r;
for(int i=1;i<=n;i++)
{
rd(id),rd(op),rd(l);if(op!=2)rd(r);
l^=las_ans,r^=las_ans;
if(op==1)ins(l,r,id,i);
if(op==2)del(l,id,i);
if(op==3)reverse(l,r,id,i);
if(op==4)qry(l,r,id,i);
}
}
3.可持久化并查集
用可持久化线段树维护每个点每个版本的父亲,因为不能加路径压缩所以只用按秩合并保证树高在log,所以每次修改查到一个点父亲要log^2的复杂度,总复杂度n*log^2(注意这题询问数和节点数并不相同,空间要算好......)
#include
#define pii pair
#define fi first
#define sc second
using namespace std;
const int N=2e5+100,M=8e6+100;
template
void rd(T &x)
{
char c=getchar();x=0;bool f=0;
while(!isdigit(c))f|=(c=='-'),c=getchar();
while(isdigit(c))x=x*10+c-48,c=getchar();
if(f)x=-x;
}
struct Seg{
int ls,rs,fa,ht;
}seg[M];
int n,m,tot=0;
int edit[N];
int build(int l,int r)
{
int nw=++tot;
if(l==r)
{
seg[nw].ls=seg[nw].rs=0;
seg[nw].fa=l,seg[nw].ht=1;
return nw;
}
int mid=(l+r)>>1;
seg[nw].ls=build(l,mid),seg[nw].rs=build(mid+1,r);
return nw;
}
pii qry(int nw,int l,int r,int to)
{
if(l==r)return pii(seg[nw].fa,seg[nw].ht);
int mid=(l+r)>>1;
if(to<=mid)return qry(seg[nw].ls,l,mid,to);
else return qry(seg[nw].rs,mid+1,r,to);
}
pii find_fa(int id,int x)
{
int bf=x;
pii nw=qry(edit[id],1,n,x);
while(nw.fi!=bf)
{
bf=nw.fi,
nw=qry(edit[id],1,n,nw.fi);
}
return nw;
}
int cg(int bf,int l,int r,int to,pii val)
{
int nw=++tot;
seg[nw]=seg[bf];
if(l==r)seg[nw].fa=val.fi,seg[nw].ht=val.sc;
else
{
int mid=(l+r)>>1;
if(to<=mid)seg[nw].ls=cg(seg[bf].ls,l,mid,to,val);
else seg[nw].rs=cg(seg[bf].rs,mid+1,r,to,val);
}
return nw;
}
int main()
{
int x,y,op;pii fx,fy;
rd(n),rd(m);
edit[0]=build(1,n);
for(int i=1;i<=m;i++)
{
rd(op),rd(x);if(op!=2)rd(y);
if(op==1)
{
fx=find_fa(i-1,x),fy=find_fa(i-1,y);
if(fx.sc>fy.sc)swap(fx,fy);
edit[i]=cg(edit[i-1],1,n,fx.fi,pii(fy.fi,fx.sc));
if(fx.sc==fy.sc)edit[i]=cg(edit[i],1,n,fy.fi,pii(fy.fi,fy.sc+1));
}
if(op==2)edit[i]=edit[x];
if(op==3)
{
edit[i]=edit[i-1];
fx=find_fa(i-1,x),fy=find_fa(i-1,y);
if(fx.fi==fy.fi)puts("1");
else puts("0");
}
}
}
4.可持久化trie树
这个东西其实和线段树平衡树差不多,插入一个数多log个点,全都新开即可。
看到洛谷训练有个专题叫可持久化trie,本来打算单独开一篇博客刷那里的题,结果发现没一道是真的trie......所以就去再用可持久化trie写了一波可持久化平衡树模板题.
#include
using namespace std;
const int N=5e5+100;
const int M=2e7+100;
const int inf=1e9+5,lim=1<<30;
template
void rd(T &x)
{
char c=getchar();x=0;bool f=0;
while(!isdigit(c))f|=(c=='-'),c=getchar();
while(isdigit(c))x=x*10+c-48,c=getchar();
if(f)x=-x;
}
int n,nxt[M][2],sz[M],tot=0,edit[N];
void push_up(int x)
{sz[x]=sz[nxt[x][0]]+sz[nxt[x][1]];}
int ins(int bf,int x,int mo)
{
int nw=++tot;
nxt[nw][0]=nxt[bf][0],nxt[nw][1]=nxt[bf][1];
if(mo==0)
{
sz[nw]=sz[bf]+1;
return nw;
}
if(x&mo)nxt[nw][1]=ins(nxt[bf][1],x,mo>>1);
else nxt[nw][0]=ins(nxt[bf][0],x,mo>>1);
push_up(nw);
return nw;
}
int del(int bf,int x,int mo)
{
int nw=++tot;
nxt[nw][0]=nxt[bf][0],nxt[nw][1]=nxt[bf][1];
if(mo==0)
{
if(sz[bf])sz[nw]=sz[bf]-1;
return nw;
}
if((x&mo)&&nxt[bf][1])nxt[nw][1]=del(nxt[bf][1],x,mo>>1);
if(!(x&mo)&&nxt[bf][0])nxt[nw][0]=del(nxt[bf][0],x,mo>>1);
push_up(nw);return nw;
}
int rnk(int nw,int x,int mo)
{
if(mo==0)return 0;
if(x&mo)return sz[nxt[nw][0]]+rnk(nxt[nw][1],x,mo>>1);
else return rnk(nxt[nw][0],x,mo>>1);
}
int rnk_sam(int nw,int x,int mo)
{
if(mo==0)return sz[nw];
if(x&mo)return sz[nxt[nw][0]]+rnk_sam(nxt[nw][1],x,mo>>1);
else return rnk_sam(nxt[nw][0],x,mo>>1);
}
int kth(int nw,int x,int mo)
{
if(mo==0)return 0;
if(sz[nxt[nw][0]]>=x)return kth(nxt[nw][0],x,mo>>1);
else return kth(nxt[nw][1],x-sz[nxt[nw][0]],mo>>1)+mo;
}
int main()
{
rd(n);
int id,op,x,res;
edit[0]=ins(edit[0],INT_MAX,lim);
edit[0]=ins(edit[0],0,lim);
for(int i=1;i<=n;i++)
{
rd(id),rd(op),rd(x);if(op!=4)x+=inf;
if(op==1)edit[i]=ins(edit[id],x,lim);
if(op==2)edit[i]=del(edit[id],x,lim);
if(op==3)edit[i]=edit[id],res=rnk(edit[id],x,lim);
if(op==4)edit[i]=edit[id],res=kth(edit[id],x+1,lim)-inf;
if(op==5)edit[i]=edit[id],res=kth(edit[id],rnk(edit[id],x,lim),lim)-inf;
if(op==6)edit[i]=edit[id],res=kth(edit[id],rnk_sam(edit[id],x,lim)+1,lim)-inf;
if(op>=3)
{
if(res<=-inf)res=-INT_MAX;
if(res>=inf)res=INT_MAX;
printf("%d\n",res);
}
}
}