zoukankan      html  css  js  c++  java
  • 【模板】树套树

    在这里先给出一道模板题

    【XSY2685】【LG3380】【BZOJ3196】【TYVJ1730】二逼平衡树


    (Description)

    您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:

    1.查询k在区间内的排名(一个数的排名是小于这个数的个数+1)

    2.查询区间内排名为(k)的值

    3.修改某一位值上的数值

    4.查询(k)在区间内的前驱(前驱定义为小于(x),且最大的数)

    5.查询(k)在区间内的后继(后继定义为大于(x),且最小的数)


    (Input)

    第一行两个数(n,m)表示长度为(n)的有序序列和(m)个操作

    第二行有(n)个数,表示有序序列

    下面有(m)行,(opt)表示操作标号

    (opt=1)则为操作(1),之后有三个数(l,r,k)表示查询(k)在区间([l,r])的排名

    (opt=2)则为操作(2),之后有三个数(l,r,k)表示查询区间([l,r])内排名为(k)的数

    (opt=3)则为操作(3),之后有两个数(pos,k)表示将(pos)位置的数修改为(k)

    (opt=4)则为操作(4),之后有三个数(l,r,k)表示查询区间([l,r])(k)的前驱

    (opt=5)则为操作(5),之后有三个数(l,r,k)表示查询区间([l,r])(k)的后继


    (Output)

    对于操作(1,2,4,5)各输出一行,表示查询结果


    (Sample Input)

    9 6
    4 2 2 1 9 4 0 1 1
    2 1 4 3
    3 4 10
    2 1 4 3
    1 2 5 9
    4 3 9 5
    5 2 8 5


    (Sample Output)

    2
    4
    3
    4
    9


    (HINT)

    1.(n)(m)的数据范围:(n,m≤50000)

    2 序列中每个数的数据范围:([0,108])

    3.虽然原题没有,但事实上(5)操作的(k)可能为负数

    4.保证答案一定存在


    题解


    一、这道题目在说什么?

    给你一个有序序列,需要实现:

    1. 区间查询(k)排名
    2. 区间查询排名(k)的值
    3. 单点修改权值
    4. 区间查询(k)前驱
    5. 区间查询(k)后继

    二、这道题目怎么做?

    这道题,涉及到了区间查询以及单点修改,我们反应过来:这是一道待修区间K大的题目。
    我们可爱的线段树能够实现单点修改和区间修改,但是面对区间K大就无能为力
    我们精简的主席树和功能强大的平衡树可以完成区间K大的任务,但是不能完成待修的任务

    于是我们不得不放弃普通的线段树或者平衡树做法,这时,就要用树套树做法了。

    我们在外层建一棵线段树,完成单点修改的任务
    再在每个线段树节点建一棵平衡树(可以是(treap),也可以是(splay),也可以是另外各种可以实现区间询问的平衡树)
    每次区间询问的时候,就调用线段树节点中的平衡树完成查询,在这个时候,平衡树对应的就是一个区间,而不是一整个线段树

    在这里我用的是(fhq) (treap),个人认为(fhq) (treap)容易理解,好打

    在这里就不多讲(fhq) (treap) 的代码部分,都是板子,就只讲线段树的部分

    为了方便,我将(fhq) (treap)封装成一个(struct),需要的可以看看


    三、代码实现


    1. 区间查询值(k)的排名

    我们在线段树中找到在询问区间内的节点,然后调用平衡树查询在节点的平衡树中的(k)排名

    然后我们考虑一下,如果每次都查询的是小于等于(k)的数的个数,那么合并的时候,可能会出现很多个等于(k)的值,也就是说,这样求出的排名可能会重复多出一些

    于是我们将(queryrank)定义为寻找小于(k)的数的个数,在最后的时候再(+1)

    在这里,我们看看线段树在遍历的时候的小细节,我们可以分为(3)

    1. 当整个区间都在左子树,返回左子树的查询
    2. 当整个区间都在右子树,返回右子树的查询
    3. 区间横跨左右子树,返回两个子树的查询和,不过左子树的查询区间要变成([ql,mid]),右子树变成([mid+1,qr])
    int queryrank(int k,int l,int r,int ql,int qr,int val)//询问在[ql,qr]区间内小于val的数的个数
    {
    	if(ql<=l&&r<=qr)return a[k].rank(a[k].rt,val)-1;//在询问区间内,调用平衡树,记得-1
    	int ans=0;
    	int mid=(l+r)>>1;
    	if(qr<=mid)ans=queryrank(k<<1,l,mid,ql,qr,val);//整个区间都在左子树
    	else if(ql>mid)ans=queryrank(k<<1|1,mid+1,r,ql,qr,val);//整个区间都在右子树
    	else ans=queryrank(k<<1,l,mid,ql,mid,val)+queryrank(k<<1|1,mid+1,r,mid+1,qr,val);//横跨左右子树
    	return ans;
    }
    

    2.查询排名为(k)的值

    这个操作不能树套树中实现,因为不知道怎么将答案合并起来,于是我们考虑写一个简单的二分答案

    每次二分一个值,用(queryrank)(楼上)来查询这个值的(rank)

    //无脑二分不多解释
    int queryval(int ql,int qr,int val)//询问在[ql,qr]区间内排名为val的值
    {
    	int l=0,r=1e8,ans=-1;
    	while(l<=r)
    	{
    		int mid=(l+r)>>1;	
    		if(queryrank(1,1,n,ql,qr,mid)+1<=val)ans=mid,l=mid+1;
    		else r=mid-1;
    	}
    	return ans;
    }
    

    3.修改(k)位置上的值

    我们从根往([k,k])遍历,将路径上的节点中的平衡树中的(k)位置上的值先删除,然后再加入新的值,直到遍历到([k,k]),跟往常的线段树修改没什么区别

    void change(int k,int l,int r,int pos,int val)//将pos位置的值改为val
    {
    	a[k].del(a[k].rt,p[pos]);//删除pos位置的值
    	a[k].ins(a[k].rt,val);//加入val
    	if(l==r)return ;
    	int mid=(l+r)>>1;
    	if(pos<=mid)change(k<<1,l,mid,pos,val);
    	else change(k<<1|1,mid+1,r,pos,val);
    }
    

    4. 查询(k)在区间内的前驱

    查询前驱也没什么特别的,在询问区间范围内就调用平衡树,最后取左右子树查询的(maxn)

    也可以像操作(1)那样分离

    //不再注释
    int querypre(int k,int l,int r,int ql,int qr,int val)//查询[ql,qr]区间内val的前驱
    {
    	if(l>=ql&&r<=qr)return a[k].pre(a[k].rt,val);
    	int ans=-INF;
    	int mid=(l+r)>>1;
    	if(qr<=mid)ans=max(ans,querypre(k<<1,l,mid,ql,qr,val));
    	else if(ql>mid)ans=max(ans,querypre(k<<1|1,mid+1,r,ql,qr,val));
    	else ans=max(ans,max(querypre(k<<1,l,mid,ql,mid,val),querypre(k<<1|1,mid+1,r,mid+1,qr,val)));
    	return ans;
    }
    

    5.查询(k)在区间内的后继

    同上,后继取左右子树查询的(minn)

    int querynxt(int k,int l,int r,int ql,int qr,int val)
    {
    	if(l>=ql&&r<=qr)return a[k].nxt(a[k].rt,val);
    	int mid=(l+r)>>1;
    	int ans=INF;
    	if(qr<=mid)ans=min(ans,querynxt(k<<1,l,mid,ql,qr,val));
    	else if(ql>mid)ans=min(ans,querynxt(k<<1|1,mid+1,r,ql,qr,val));
    	else ans=min(ans,min(querynxt(k<<1,l,mid,ql,mid,val),querynxt(k<<1|1,mid+1,r,mid+1,qr,val)));
    	return ans;
    }
    

    (ps:)树套树的时间复杂度都很优秀 ,所以需要注意一下卡常和各种玄学优化,(但这道题我加快读比不加慢?!)

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int INF=0x7fffffff,N=5e4+10;
    int n,m;
    int p[N];
    int cnt=0;
    struct tree
    {
    	int ch[2],siz,val,rd;
    }t[N*40];
    int seed=233;	
    struct FhqTreap
    {
    	int rt;
    	int Rand()
    	{
        	return seed=int(seed*4827111l%0x7ffffffff);
    	}
    	int newnode(int val)
    	{
    		t[++cnt].val=val;
    		t[cnt].siz=1;
    		t[cnt].rd=Rand();
    		return cnt;
    	}
    	void up(int k)
    	{
    		t[k].siz=t[t[k].ch[0]].siz+t[t[k].ch[1]].siz+1;
    	}
    	void split(int now,int val,int &a,int &b)
    	{
    		if(!now)
    		{
    			a=b=0;
    			return ;
    		}
    		if(t[now].val<=val)
    		{
    			a=now;
    			split(t[now].ch[1],val,t[a].ch[1],b);
    		}
    		else
    		{
    			b=now;
    			split(t[now].ch[0],val,a,t[b].ch[0]);
    		}
    		up(now);
    	}
    	int merge(int a,int b)
    	{
    		if(!(a&&b))return a+b;
    		if(t[a].rd<t[b].rd)
    		{
    			t[a].ch[1]=merge(t[a].ch[1],b);
    			up(a);
    			return a;
    		}
    		else 
    		{
    			t[b].ch[0]=merge(a,t[b].ch[0]);
    			up(b);
    			return b;
    		}
    	}
    	void ins(int &rt,int val)
    	{
    		int a,b;
    		int c=newnode(val);
    		split(rt,val,a,b);
    		rt=merge(merge(a,c),b);
    	}
    	void del(int &rt,int val)
    	{
    		int a,b,c;
    		split(rt,val,a,c);
    		split(a,val-1,a,b);
    		b=merge(t[b].ch[0],t[b].ch[1]);
    		a=merge(a,b);
    		rt=merge(a,c);
    	}
    	int rank(int rt,int val)
    	{
    		int a,b;
    		split(rt,val-1,a,b);
    		int ans=t[a].siz+1;
    		rt=merge(a,b);
    		return ans;
    	}
    	int kth(int now,int rk)
    	{
    		while((t[t[now].ch[0]].siz+1)!=rk)
    		{
    			if(t[t[now].ch[0]].siz>=rk)now=t[now].ch[0];
    			else 
    			{
    				rk-=t[t[now].ch[0]].siz+1;
    				now=t[now].ch[1];
    			}
    		}
    		return t[now].val;
    	}
    	int pre(int rt,int val)
    	{
    		int a,b;
    		split(rt,val-1,a,b);
    		int ans=t[a].siz?kth(a,t[a].siz):-INF;
    		rt=merge(a,b);
    		return ans;
    	}
    	int nxt(int rt,int val)
    	{
    		int a,b;
    		split(rt,val,a,b);
    		int ans=t[b].siz>0?kth(b,1):INF;
    		rt=merge(a,b);
    		return ans;
    	}
    }a[N<<2];
    void build(int k,int l,int r)
    {
    	for(int i=l;i<=r;i++)a[k].ins(a[k].rt,p[i]);
    	if(l==r)return ;
    	int mid=(l+r)>>1;
    	build(k<<1,l,mid);
    	build(k<<1|1,mid+1,r);
    }
    int queryrank(int k,int l,int r,int ql,int qr,int val)
    {
    	if(ql<=l&&r<=qr)return a[k].rank(a[k].rt,val)-1;
    	int ans=0;
    	int mid=(l+r)>>1;
    	if(qr<=mid)ans=queryrank(k<<1,l,mid,ql,qr,val);
    	else if(ql>mid)ans=queryrank(k<<1|1,mid+1,r,ql,qr,val);
    	else ans=queryrank(k<<1,l,mid,ql,mid,val)+queryrank(k<<1|1,mid+1,r,mid+1,qr,val);
    	return ans;
    }
    int queryval(int ql,int qr,int val)
    {
    	int l=0,r=1e8,ans=-1;
    	while(l<=r)
    	{
    		int mid=(l+r)>>1;	
    		if(queryrank(1,1,n,ql,qr,mid)+1<=val)ans=mid,l=mid+1;
    		else r=mid-1;
    	}
    	return ans;
    }
    void change(int k,int l,int r,int pos,int val)
    {
    	a[k].del(a[k].rt,p[pos]);
    	a[k].ins(a[k].rt,val);
    	if(l==r)return ;
    	int mid=(l+r)>>1;
    	if(pos<=mid)change(k<<1,l,mid,pos,val);
    	else change(k<<1|1,mid+1,r,pos,val);
    }
    int querypre(int k,int l,int r,int ql,int qr,int val)
    {
    	if(l>=ql&&r<=qr)return a[k].pre(a[k].rt,val);
    	int ans=-INF;
    	int mid=(l+r)>>1;
    	if(qr<=mid)ans=max(ans,querypre(k<<1,l,mid,ql,qr,val));
    	else if(ql>mid)ans=max(ans,querypre(k<<1|1,mid+1,r,ql,qr,val));
    	else ans=max(ans,max(querypre(k<<1,l,mid,ql,mid,val),querypre(k<<1|1,mid+1,r,mid+1,qr,val)));
    	return ans;
    }
    int querynxt(int k,int l,int r,int ql,int qr,int val)
    {
    	if(l>=ql&&r<=qr)return a[k].nxt(a[k].rt,val);
    	int mid=(l+r)>>1,ans=INF;
    	if(qr<=mid)ans=min(ans,querynxt(k<<1,l,mid,ql,qr,val));
    	else if(ql>mid)ans=min(ans,querynxt(k<<1|1,mid+1,r,ql,qr,val));
    	else ans=min(ans,min(querynxt(k<<1,l,mid,ql,mid,val),querynxt(k<<1|1,mid+1,r,mid+1,qr,val)));
    	return ans;
    }
    inline int read()
    {
    	int x=0,f=1;
    	char ch=getchar();
    	while(!isdigit(ch))
    	{
    		if(ch=='-')f=-1;
    		ch=getchar();
    	}
    	while(isdigit(ch))
    	{
    		x=(x<<3)+(x<<1)+(ch^48);
    		ch=getchar();
    	}
    	return x*f;
    }
    int main()
    {
    	srand(19260817);
    	n=read(),m=read();
    	for(int i=1;i<=n;i++)p[i]=read();
    	build(1,1,n);
    	int op,l,r,k,pos;
    	for(int i=1;i<=m;i++)
    	{
    		op=read();
    		if(op==1)
    		{
    			l=read(),r=read(),k=read();
    			printf("%d
    ",queryrank(1,1,n,l,r,k)+1);
    		}
    		if(op==2)
    		{
    			l=read(),r=read(),k=read();
    			printf("%d
    ",queryval(l,r,k));
    		}
    		if(op==3)
    		{
    			pos=read(),k=read();
    			change(1,1,n,pos,k);
    			p[pos]=k;
    		}
    		if(op==4)
    		{
    			l=read(),r=read(),k=read();
    			printf("%d
    ",querypre(1,1,n,l,r,k));
    		}
    		if(op==5)
    		{
    			l=read(),r=read(),k=read();
    			printf("%d
    ",querynxt(1,1,n,l,r,k));
    		}
    	}
    	return 0;
    }
    /*
    9 6
    4 2 2 1 9 4 0 1 1
    2 1 4 3
    3 4 10
    2 1 4 3
    1 2 5 9
    4 3 9 5
    5 2 8 5
    */
    

  • 相关阅读:
    Session的使用与Session的生命周期
    Long-Polling, Websockets, SSE(Server-Sent Event), WebRTC 之间的区别与使用
    十九、详述 IntelliJ IDEA 之 添加 jar 包
    十八、IntelliJ IDEA 常用快捷键 之 Windows 版
    十七、IntelliJ IDEA 中的 Maven 项目初体验及搭建 Spring MVC 框架
    十六、详述 IntelliJ IDEA 创建 Maven 项目及设置 java 源目录的方法
    十五、详述 IntelliJ IDEA 插件的安装及使用方法
    十四、详述 IntelliJ IDEA 提交代码前的 Code Analysis 机制
    十三、IntelliJ IDEA 中的版本控制介绍(下)
    十二、IntelliJ IDEA 中的版本控制介绍(中)
  • 原文地址:https://www.cnblogs.com/ShuraEye/p/11396359.html
Copyright © 2011-2022 走看看