可持久化數據結構

1.可持久化線段樹(可持久化數組)

https://www.luogu.org/problemnew/show/P3919#sub

最基礎的可持久化數據結構,每次修改開新的log個點即可。

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+100;

template<class T>
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<class T>
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<bits/stdc++.h>
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<bits/stdc++.h>
using namespace std;
const int N=5e5+100;
const int inf=INT_MAX;

template<class T>
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<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+100;
const int M=3e7+100;

template<class T>
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<bits/stdc++.h>
#define pii pair<int,int>
#define fi first
#define sc second
using namespace std;
const int N=2e5+100,M=8e6+100;

template<class T>
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個點,全都新開即可

題目比較多單獨寫了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章