可持久化数据结构

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);
		}
	}
}

 

你可能感兴趣的:(训练集,刷题集,不常用但有时很有用的东西)