• 可持久化数据结构


    区间第K小查询

    description

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

    (nle 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,mle 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
    ",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
    ",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;
    }
    
    NO PAIN NO GAIN
  • 相关阅读:
    python3----数据结构
    Java的同步容器和并发容器
    Java基础——IO
    JVM(2)——GC算法和收集器
    Java集合(2)——深入理解ArrayList、Vector和LinkedList
    java线程(7)——阻塞队列BlockingQueue
    JVM(1)——简介
    java泛型——基本使用
    java线程(6)——线程池(下)
    java线程(5)——线程池(上)
  • 原文地址:https://www.cnblogs.com/zmyzmy/p/14854310.html
Copyright © 2020-2023  润新知