码迷,mamicode.com
首页 > 其他好文 > 详细

可持久化数据结构

时间:2021-06-06 19:07:20      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:putchar   dig   个数   clear   line   数据结构   平衡   无法   结构   

区间第K小查询

description

给定一个长度为\(n\) 的序列,每次对于一个区间\([l,r]\) ,求出这段区间中第\(k\) 小的数的值。

\(n\le 10^5\)

solution

首先考虑全局怎么做,即询问区间为\([1,n]\) 时。

我们可以建立权值线段树,对于其上的区间\([l,r]\) 记下全局有多少个数在\([l,r]\) 之间,查询时只用在其上二分即可。

倘若询问区间,我们也可以建出这段区间的权值线段树,然后查询,但显然复杂度爆炸。

受到前缀和优化区间和的启发,我们可以想到建立前缀的权值线段树,这样\([l,r]\) 的区间线段树就是\(r\) 的线段树减去\(l-1\) 的。如果每次暴力建还是无法承受,但注意到\(i\) 处的线段树是由\(i-1\) 处的线段树修改\(\mathcal O(\log n)\) 个节点得来的,于是直接上可持久化即可。

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,m,s,cnt;
int a[N],b[N],root[N];
int sum[N<<5],L[N<<5],R[N<<5];
int build(int l,int r)
{
    int rr=++cnt;
    sum[rr]=0;
    if(l==r) return rr;
    int mid=(l+r)/2;
    L[rr]=build(l,mid);
    R[rr]=build(mid+1,r);
    return rr;
}
int UP(int kk,int l,int r,int x)
{
    int rr=++cnt;
    sum[rr]=sum[kk]+1;
    L[rr]=L[kk];
    R[rr]=R[kk];
    if(l==r) return rr;
    int mid=(l+r)/2;
    if(x<=mid)
        L[rr]=UP(L[kk],l,mid,x);
    else
        R[rr]=UP(R[kk],mid+1,r,x);
    return rr;
}
int Query(int u,int v,int l,int r,int k)
{
    if(l==r) return l;
    int xx=sum[L[v]]-sum[L[u]];
    int mid=(l+r)/2;
    if(xx>=k)
        return Query(L[u],L[v],l,mid,k);
    else
        return Query(R[u],R[v],mid+1,r,k-xx);
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        b[i]=a[i];
    }
    sort(b+1,b+n+1);
    s=unique(b+1,b+n+1)-(b+1);
    root[0]=build(1,s);
    for(int i=1;i<=n;i++)
    {
        int t=lower_bound(b+1,b+s+1,a[i])-b;
        root[i]=UP(root[i-1],1,s,t);
    }
    while(m--)
    {
        int o,p,q;
        cin>>o>>p>>q;
        cout<<b[Query(root[o-1],root[p],1,s,q)]<<endl;
    }
    return 0;
}

树上计数

description

给出一个\(n\) 个结点的树,每个结点有一个整数权值。执行\(m\) 次询问,每次询问给出\(u,v,k\) ,求其简单路径上的第\(k\)小点权值。\(n,m\le 10^5\)

solution

和上一道题类似,维护前缀权值线段树然后差分得到这条路径对应的线段树,是树上差分的技巧。不同点是\(u\) 的前缀线段树是由\(u\) 的父亲继承而来的。

code

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,H=17;
int n,m,mx,rt[N],val[N],fa[N][20],tin[N],tout[N],tim,dep[N],cv[N];
vector<int>e[N];
struct SGT
{
	int tot,ls[N<<5],rs[N<<5],sz[N<<5];
	void upd(int&rt,int prt,int v,int l,int r)
	{
		rt=++tot;
		ls[rt]=ls[prt],rs[rt]=rs[prt];
		sz[rt]=sz[prt]+1;
		if(l==r)return;int mid=l+r>>1;
		if(v<=mid)upd(ls[rt],ls[prt],v,l,mid);
		else upd(rs[rt],rs[prt],v,mid+1,r);
	}
	int query(int x,int y,int ac,int f,int k,int l,int r)
	{
		if(l==r)return l;int mid=l+r>>1;
		int num=sz[ls[x]]+sz[ls[y]]-sz[ls[ac]]-sz[ls[f]];
		if(k<=num)return query(ls[x],ls[y],ls[ac],ls[f],k,l,mid);
		return query(rs[x],rs[y],rs[ac],rs[f],k-num,mid+1,r);
	}
}T;
inline int read()
{
	int s=0,w=1; char ch=getchar();
	for(;!isdigit(ch);ch=getchar())if(ch==‘-‘)w=-1;
	for(;isdigit(ch);ch=getchar())s=(s<<1)+(s<<3)+(ch^48);
	return s*w;
}
inline void lsh()
{
	copy(val+1,val+n+1,cv+1);
	sort(cv+1,cv+n+1);
	mx=unique(cv+1,cv+n+1)-cv-1;
	for(int i=1;i<=n;++i)
		val[i]=lower_bound(cv+1,cv+mx+1,val[i])-cv;
}
inline void add(int x,int y){e[x].push_back(y);}
void dfs(int u,int pr)
{
	fa[u][0]=pr;dep[u]=dep[pr]+1;tin[u]=++tim;
	for(int i=1;(1<<i)<=dep[u];++i)
		fa[u][i]=fa[fa[u][i-1]][i-1];
	T.upd(rt[u],rt[pr],val[u],1,mx);
	for(int i=0;i<e[u].size();++i)
	{
		int v=e[u][i];
		if(v!=pr)dfs(v,u);
	}tout[u]=++tim;
}
inline bool isac(int x,int y){return tin[x]<=tin[y]&&tout[y]<=tout[x];}
inline int lca(int x,int y)
{
	if(dep[x]>dep[y])swap(x,y);
	if(isac(x,y))return x;
	for(int i=H;~i;--i)
		if(!isac(fa[x][i],y))x=fa[x][i];
	return fa[x][0];
}
int main()
{
	n=read(),m=read();
	for(int i=1;i<=n;++i)val[i]=read();lsh();
	for(int i=1,u,v;i<n;++i)
	{
		u=read(),v=read();
		add(u,v),add(v,u);
	}dfs(1,0);tin[0]=0,tout[0]=++tim;
	while(m--)
	{
		int u=read(),v=read(),k=read();
		int p=lca(u,v);
		printf("%d\n",cv[T.query(rt[u],rt[v],rt[p],rt[fa[p][0]],k,1,mx)]);
	}
	return 0;
}

树上异或

description

给定一棵\(n\) 个节点的树,点有点权,多次询问树上简单路径上所有点权值与给定值\(d\) 的异或最大值。

solution

仍然考虑如果放到全局怎么做。这是一个经典问题,即对于每个权值按二进制位从高到低地插入到\(01Trie\) 中,询问时也是从高位到低位考虑,当前位能异或得到\(1\) 那么就贪心地选择,因为即使后面的位全部是\(1\) 也不能大过当前位的\(1\)

搬到树上。和上一道题也是类似的树上差分,只不过将主席树替换为了可持久化\(01Trie\) 。可持久化的方法是一致的。(感觉\(01Trie\) 和线段树在某种意义上是等价的)

code

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,H=16;
namespace Trie
{
	int ch[N<<5][2],sz[N<<5],tot;
	inline void pre(){tot=0;}
	inline int nd()
	{
		int p=++tot;ch[p][0]=ch[p][1]=0;
		sz[p]=0;return p;
	}
	void ins(int&rt,int prt,int x,int dep=H)
	{
		rt=nd();
		ch[rt][0]=ch[prt][0],ch[rt][1]=ch[prt][1];
		sz[rt]=sz[prt]+1;
		if(!(~dep))return;
		int c=(x>>dep)&1;
		ins(ch[rt][c],ch[prt][c],x,dep-1);
	}
	int query(int a,int b,int c,int d,int x,int dep=H)
	{
		if(!(~dep))return 0;
		int t=(x>>dep)&1;t^=1;
		int s=sz[ch[a][t]]+sz[ch[b][t]]-sz[ch[c][t]]-sz[ch[d][t]];
		if(s)return (1<<dep)+query(ch[a][t],ch[b][t],ch[c][t],ch[d][t],x,dep-1);
		t^=1;return query(ch[a][t],ch[b][t],ch[c][t],ch[d][t],x,dep-1);
	}
}
int n,m,rt[N],val[N],in[N],out[N],tim,dep[N],fa[N][20];
vector<int>e[N];
void dfs(int u,int f)
{
	in[u]=++tim;dep[u]=dep[f]+1;
	fa[u][0]=f;
	for(int i=1;i<=H;++i)
		fa[u][i]=fa[fa[u][i-1]][i-1];
	rt[u]=rt[f];Trie::ins(rt[u],rt[u],val[u]);
	for(int v:e[u])if(v^f)dfs(v,u);
	out[u]=++tim;
}
inline bool isac(int x,int y){return in[x]<=in[y]&&out[y]<=out[x];}
inline int lca(int x,int y)
{
	if(dep[x]>dep[y])swap(x,y);
	if(isac(x,y))return x;
	for(int i=H;~i;--i)
		if(!isac(fa[x][i],y))x=fa[x][i];
	return fa[x][0];
}
int main()
{
	while(scanf("%d%d",&n,&m)==2)
	{
		for(int i=1;i<=n;++i)scanf("%d",val+i);
		for(int i=1,u,v;i<n;++i)
			scanf("%d%d",&u,&v),
			e[u].push_back(v),e[v].push_back(u);
		Trie::pre();tim=0;dfs(1,0);out[0]=++tim;
		while(m--)
		{
			int x,y,d;scanf("%d%d%d",&x,&y,&d);
			int p=lca(x,y);
			printf("%d\n",Trie::query(rt[x],rt[y],rt[p],rt[fa[p][0]],d));
		}
		for(int i=1;i<=n;++i)e[i].clear();
	}
	return 0;
}

自带版本控制功能的IDE

description

维护一种数据结构,支持三种操作。

1.在p位置插入一个字符串s

2.从p位置开始删除长度为c的字符串

3.输出第v个历史版本中从p位置开始的长度为c的字符串

强制在线。字符串总长度不超过\(10^6\)

solution

可持久化平衡树模板。

可持久化平衡树一般采用可持久化非旋\(Treap\) 。考虑只有\(split\)\(merge\) 两种操作会改变树的形态,于是只在这两个操作时需要复制节点以保留原先的版本。其他操作和普通\(Treap\) 相同。

不过由于\(merge\) 时的节点其实就是\(split\) 时新创的节点,因此\(merge\) 时也不需要新建节点。

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int ddd;
mt19937 rd(time(0));
namespace Treap
{
	int tot=0;struct node{int ls,rs,v,sz,fix;}t[N<<5];
	inline int nd(int r)
	{
		int p=++tot;t[p]={0,0,r,1,rd()};
		return p;
	}
	inline void upd(int x){t[x].sz=t[t[x].ls].sz+t[t[x].rs].sz+1;}
	void split(int p,int d,int&l,int&r)
	{
		if(!p){l=r=0;return;}
		int now=++tot;t[now]=t[p];
		if(t[t[p].ls].sz+1<=d)
		{
			l=now,split(t[p].rs,d-1-t[t[p].ls].sz,t[l].rs,r);
			upd(l);
		}
		else
		{
			r=now,split(t[p].ls,d,l,t[r].ls);
			upd(r);
		}
	}
	int merge(int l,int r)
	{
		if(!l||!r)return l^r;
		if(t[l].fix>t[r].fix)
		{
			t[l].rs=merge(t[l].rs,r);
			upd(l);return l;
		}
		else
		{
			t[r].ls=merge(l,t[r].ls);
			upd(r);return r;
		}
	}
	inline void ins(int&rt,int p,char*s)
	{
		int len=strlen(s);
		int a,b;split(rt,p,a,b);
		for(int i=0;i<len;++i)
			a=merge(a,nd(s[i]));
		rt=merge(a,b);
	}
	inline void del(int&rt,int p,int t)
	{
		int a,b,c;
		split(rt,p-1,a,b);
		split(b,t,b,c);
		rt=merge(a,c);
	}
	void go(int u)
	{
		if(!u)return;
		go(t[u].ls);
		putchar(t[u].v);if(t[u].v==‘c‘)++ddd;
		go(t[u].rs);
	}
	inline void print(int&rt,int p,int t)
	{
		int a,b,c;
		split(rt,p-1,a,b);
		split(b,t,b,c);
		go(b);puts("");
		rt=merge(merge(a,b),c);
	}
}
using namespace Treap;
int rt[N];char s[N];
int main()
{
	int q;scanf("%d",&q);int now=0;
	while(q--)
	{
		int opt,p,c,v;scanf("%d",&opt);
		if(opt==1)
		{
			++now;rt[now]=rt[now-1];
			scanf("%d%s",&p,s);p-=ddd;
			ins(rt[now],p,s);
		}
		else if(opt==2)
		{
			++now;rt[now]=rt[now-1];
			scanf("%d%d",&p,&c);p-=ddd,c-=ddd;
			del(rt[now],p,c);
		}
		else
		{
			scanf("%d%d%d",&v,&p,&c);
			v-=ddd,p-=ddd,c-=ddd;
			print(rt[v],p,c);
		}
	}
	return 0;
}

可持久化数据结构

标签:putchar   dig   个数   clear   line   数据结构   平衡   无法   结构   

原文地址:https://www.cnblogs.com/zmyzmy/p/14854310.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!