• 【模板】树套树


    在这里先给出一道模板题

    【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
    */
    

  • 相关阅读:
    TinyEditor:简洁且易用的html所见即所得编辑器
    arguments.callee 调用自身
    java.io.IOException: 设备未就绪
    关于vcastr3播放器自动播放的问题
    javascript中IE浏览器不支持NEW DATE()带参数的解决方法
    Oracle常用查看表结构命令
    java通过文件头内容判断文件类型
    RS开发日期提示控件默认为昨天
    Cognos更新问题之利用Transform实现Cube增量更新
    SqlServer中从字符串中获取项目指标方法charindex月substring结合
  • 原文地址:https://www.cnblogs.com/ShuraEye/p/11396359.html
Copyright © 2020-2023  润新知