权值线段树套序列线段树

Posted brotherhood

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了权值线段树套序列线段树相关的知识,希望对你有一定的参考价值。

【模板】权值线段树套序列线段树

P3380 【模板】二逼平衡树(树套树)

主要思路如下:

外层为权值线段树,内层为动态开点线段树,也就是每个权值线段树上的节点开一个动态开点线段树。

外层的权值线段树支持查询排名,内层的线段树限制了区间。实际上就是在普通权值线段树上查询的价值变成了在其线段树上区间查询返回的值。

对于这道模板题,我们先写几个函数:

插入

下方的update为外层线段树,将路径上的所有点的线段树修改。

上方的update为内层线段树,动态开点单点修改。

要判断k==1为题目需要,因为如果是删除的话就不需要开点了。

变量解释:v为权值,p为下标,k为修改的值。

void update(int &ro,int l,int r,int p,int k){
    if(k==1 and !ro)ro=++tot;
    if(ro) val[ro]+=k;
    if(!ro or l==r)return;
    int mid=l+r>>1;
    (p<=mid)?update(ls[ro],l,mid,p,k):update(rs[ro],mid+1,r,p,k);
}
void update(int &ro,int l,int r,int v,int p,int k){
    if(k==1 and !ro)ro=++Tot;
    if(ro) update(rt[ro],1,n,p,k);
    if(!ro or l==r)return;
    int mid=l+r>>1;
    (v<=mid)?update(Ls[ro],l,mid,v,p,k):update(Rs[ro],mid+1,r,v,p,k);
}

查询

本题有多个查询:

  1. 查询k在区间中的排名,我们需要查询0~k-1在区间中的个数将结果加1。(因为有可能有重复元素)
  2. 查询区间内排名为k的值,我们只需要在权值线段树上查询即可,一个点的贡献就是其线段树在区间内的贡献。
  3. 查询前驱和后继,就是将上面两种操作合并,先查询有多少点在区间内小于/小于等于该值,然后查询排名即可。

注意判断边界情况。

int query(int ro,int l,int r,int x,int y){
    if(!ro)return 0;
    if(x<=l and y>=r)return val[ro];
    int mid=l+r>>1;
    return (x<=mid?query(ls[ro],l,mid,x,y):0)+(y>mid?query(rs[ro],mid+1,r,x,y):0);
}
int rank(int ro,int l,int r,int x,int y,int L,int R){
    if(!ro)return 0;
    if(x<=l and y>=r)return query(rt[ro],1,n,L,R);
    int mid=l+r>>1;
    return (x<=mid?rank(Ls[ro],l,mid,x,y,L,R):0)+(y>mid?rank(Rs[ro],mid+1,r,x,y,L,R):0);
}
int kth(int ro,int l,int r,int k,int L,int R){
    if(l==r)return l;
    int mid=l+r>>1,tmp=query(rt[Ls[ro]],1,n,L,R);
    return (k<=tmp)?kth(Ls[ro],l,mid,k,L,R):kth(Rs[ro],mid+1,r,k-tmp,L,R);
}
inline int pre(int x,int y,int k){
    int zp;
    if(!(zp=rank(root,0,maxl,0,k-1,x,y)))return -2147483647;
    return kth(root,0,maxl,zp,x,y);
}
inline int nxt(int x,int y,int k){
    int zp;
    if((zp=rank(root,0,maxl,0,k,x,y))==rank(root,0,maxl,0,maxl,x,y))return 2147483647;
    return kth(root,0,maxl,zp+1,x,y);
}

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cctype>
#include<algorithm>
#include<cmath>
using namespace std;
inline int read(){
	int w=0,x=0;char c=getchar();
	while(!isdigit(c))w|=c==‘-‘,c=getchar();
	while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return w?-x:x;
}
namespace star
{
	const int maxn=5e4+10,maxm=1e7+10,maxl=1e8;
	int n,q,rt[maxm],ls[maxm*3],rs[maxm*3],Ls[maxm],Rs[maxm],val[maxm*3],tot,Tot,root;
	int a[maxn];
	void update(int &ro,int l,int r,int p,int k){
		if(k==1 and !ro)ro=++tot;
		if(ro) val[ro]+=k;
		if(!ro or l==r)return;
		int mid=l+r>>1;
		(p<=mid)?update(ls[ro],l,mid,p,k):update(rs[ro],mid+1,r,p,k);
	}
	void update(int &ro,int l,int r,int v,int p,int k){
		if(k==1 and !ro)ro=++Tot;
		if(ro) update(rt[ro],1,n,p,k);
		if(!ro or l==r)return;
		int mid=l+r>>1;
		(v<=mid)?update(Ls[ro],l,mid,v,p,k):update(Rs[ro],mid+1,r,v,p,k);
	}
	int query(int ro,int l,int r,int x,int y){
		if(!ro)return 0;
		if(x<=l and y>=r)return val[ro];
		int mid=l+r>>1;
		return (x<=mid?query(ls[ro],l,mid,x,y):0)+(y>mid?query(rs[ro],mid+1,r,x,y):0);
	}
	int rank(int ro,int l,int r,int x,int y,int L,int R){
		if(!ro)return 0;
		if(x<=l and y>=r)return query(rt[ro],1,n,L,R);
		int mid=l+r>>1;
		return (x<=mid?rank(Ls[ro],l,mid,x,y,L,R):0)+(y>mid?rank(Rs[ro],mid+1,r,x,y,L,R):0);
	}
	int kth(int ro,int l,int r,int k,int L,int R){
		if(l==r)return l;
		int mid=l+r>>1,tmp=query(rt[Ls[ro]],1,n,L,R);
		return (k<=tmp)?kth(Ls[ro],l,mid,k,L,R):kth(Rs[ro],mid+1,r,k-tmp,L,R);
	}
	inline int pre(int x,int y,int k){
		int zp;
		if(!(zp=rank(root,0,maxl,0,k-1,x,y)))return -2147483647;
		return kth(root,0,maxl,zp,x,y);
	}
	inline int nxt(int x,int y,int k){
		int zp;
		if((zp=rank(root,0,maxl,0,k,x,y))==rank(root,0,maxl,0,maxl,x,y))return 2147483647;
		return kth(root,0,maxl,zp+1,x,y);
	}
	inline void work(){
		n=read(),q=read();
		for(int i=1;i<=n;i++) update(root,0,maxl,a[i]=read(),i,1);
		while(q--){
			int op=read(),x=read(),y=read();
			switch(op){
				case 1:printf("%d
",rank(root,0,maxl,0,read()-1,x,y)+1);break;
				case 2:printf("%d
",kth(root,0,maxl,read(),x,y));break;
				case 3:update(root,0,maxl,a[x],x,-1),update(root,0,maxl,a[x]=y,x,1);break;
				case 4:printf("%d
",pre(x,y,read()));break;
				case 5:printf("%d
",nxt(x,y,read()));break;
			}
		}
	}
}
signed main(){
	star::work();
	return 0;
}

P3332 [ZJOI2013]K大数查询

只剩一个询问查询k大数,区间修改。具体就是把上面内层的线段树变成了区间修改的。

常数巨大的代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cctype>
#include<algorithm>
#include<cmath>
#define int long long
using namespace std;
inline int read(){
	int w=0,x=0;char c=getchar();
	while(!isdigit(c))w|=c==‘-‘,c=getchar();
	while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return w?-x:x;
}
namespace star
{
	const int maxn=5e4+10,maxm=1e7+10,maxl=5e5+10;
	int n,q,rt[maxm],ls[maxm*3],rs[maxm*3],Ls[maxm],Rs[maxm],val[maxm*3],tag[maxm*3],tot,Tot,root;
	void pushdown(int ro,int l,int r){
		int mid=floor((l+r)/2.0);
		if(!tag[ro])return;
		if(!ls[ro])ls[ro]=++tot;
		if(!rs[ro])rs[ro]=++tot;
		tag[ls[ro]]+=tag[ro],tag[rs[ro]]+=tag[ro];
		val[ls[ro]]+=tag[ro]*(mid-l+1),val[rs[ro]]+=tag[ro]*(r-mid);
		tag[ro]=0;
	}
	void pushup(int ro){
		val[ro]=val[ls[ro]]+val[rs[ro]];
	}
	void update(int &ro,int l,int r,int x,int y,int k){
		if(!ro)ro=++tot;
		if(l==x and r==y)return (void)(tag[ro]+=k,val[ro]+=k*(r-l+1));
		pushdown(ro,l,r);
		int mid=floor((l+r)/2.0);
		if(y<=mid) update(ls[ro],l,mid,x,y,k);
		else if(x>mid) update(rs[ro],mid+1,r,x,y,k);
		else update(ls[ro],l,mid,x,mid,k),update(rs[ro],mid+1,r,mid+1,y,k);
		pushup(ro);
	}
	void update(int &ro,int l,int r,int v,int x,int y,int k){
		if(!ro)ro=++Tot;
		if(ro) update(rt[ro],1,n,x,y,k);
		if(l==r)return;
		int mid=floor((l+r)/2.0);
		(v<=mid)?update(Ls[ro],l,mid,v,x,y,k):update(Rs[ro],mid+1,r,v,x,y,k);
	}
	int query(int ro,int l,int r,int x,int y){
		if(!ro)return 0;
		if(x==l and y==r)return val[ro];
		pushdown(ro,l,r);
		int mid=floor((l+r)/2.0);
		if(y<=mid) return query(ls[ro],l,mid,x,y);
		else if(x>mid) return query(rs[ro],mid+1,r,x,y);
		else return query(ls[ro],l,mid,x,mid)+query(rs[ro],mid+1,r,mid+1,y);
	}
	int kth(int ro,int l,int r,int k,int L,int R){
		if(l==r)return l;
		int mid=floor((l+r)/2.0),tmp=query(rt[Rs[ro]],1,n,L,R);
		return (k<=tmp)?kth(Rs[ro],mid+1,r,k,L,R):kth(Ls[ro],l,mid,k-tmp,L,R);
	}
	inline void work(){
		n=read(),q=read();
		while(q--){
			int op=read(),l=read(),r=read(),x=read();
			switch(op){
				case 1:update(root,-maxl,maxl,x,l,r,1);break;
				case 2:printf("%lld
",kth(root,-maxl,maxl,x,l,r));break;
			}
		}
	}
}
signed main(){
	star::work();
	return 0;
}

以上是关于权值线段树套序列线段树的主要内容,如果未能解决你的问题,请参考以下文章

树套树Day2

BZOJ3110 ZJOI2013 K大数查询 线段树套线段树

树套树初探

题解 P2617 Dynamic Rankings

bzoj3065带插入区间K小值 替罪羊树套权值线段树

[bzoj3065] 带插入区间第k小值 [重量平衡树套线段树]