[Zjoi2013]K大数查询——树套树

题目

[Zjoi2013]K大数查询

题解

题意大概是给你n个vector,对若干个编号连续的vector进行插入和查询。

典型的树套树呗

先对所有插入的c值建一棵线段树,然后每个节点再维护一棵线段树,叶子节点的线段树存该值在序列中的分布情况(也就是该值在序列中每个vector中的出现次数,维护区间和),父节点的线段树存限定区间内的所有c值在序列中的分布情况,也就是所有子节点的线段树合并。

插入就是对大线段树单点修改,对链上每个小线段树进行区间加;

查询就是利用大线段树进行二分答案,每次对小线段树进行区间查询,总复杂度O(n\log^2n)

树套树的码量名不虚传,好在我板子打得优美,少调试。

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#define ll long long
#define MAXN 50005
#define MAXP 20000005
using namespace std;
inline ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+s-'0',s=getchar();
	return f?x:-x;
}
int n;

struct itn{    //小线段树
	int ls,rs,lz;ll a;
	itn(){ls=rs=lz=a=0;}
}t[MAXP];
int IN;
inline void pushd(int x,int l,int r){
	if(t[x].lz>0){
		if(l==r){t[x].lz=0;return;}
		int mid=(l+r)>>1;
		if(!t[x].ls)t[x].ls=++IN,t[IN]=itn();
		t[t[x].ls].a+=(1ll+mid-l)*t[x].lz,t[t[x].ls].lz+=t[x].lz;
		if(!t[x].rs)t[x].rs=++IN,t[IN]=itn();
		t[t[x].rs].a+=(0ll+r-mid)*t[x].lz,t[t[x].rs].lz+=t[x].lz;
		t[x].lz=0;
	}
}
inline void addt(int x,int l,int r,int a,int b){
	if(l==a&&r==b){
		t[x].a+=r-l+1,t[x].lz++;
		return;
	}
	int mid=(l+r)>>1;pushd(x,l,r);
	if(a<=mid){
		if(!t[x].ls)t[x].ls=++IN,t[IN]=itn();
		addt(t[x].ls,l,mid,a,min(b,mid));
	}
	if(b>mid){
		if(!t[x].rs)t[x].rs=++IN,t[IN]=itn();
		addt(t[x].rs,mid+1,r,max(a,mid+1),b);
	}
	t[x].a=t[t[x].ls].a+t[t[x].rs].a;
}
inline ll sch(int x,int l,int r,int a,int b){
	if(!x)return 0;
	if(l==a&&r==b)return t[x].a;
	int mid=(l+r)>>1;pushd(x,l,r);
	ll res=0;
	if(a<=mid)res+=sch(t[x].ls,l,mid,a,min(b,mid));
	if(b>mid)res+=sch(t[x].rs,mid+1,r,max(a,mid+1),b);
	return res;
}

struct ITN{    //大线段树
	int ls,rs,id;
}tr[MAXN<<4];
int NN;
inline void add(int x,int l,int r,int a,int b,int z){
	if(l==r){addt(tr[x].id,1,n,a,b);return;}
	int mid=(l+r)>>1;
	if(z<=mid){
		if(!tr[x].ls)tr[x].ls=++NN,tr[tr[x].ls].id=++IN,t[IN]=itn();
		add(tr[x].ls,l,mid,a,b,z);
	}
	else{
		if(!tr[x].rs)tr[x].rs=++NN,tr[tr[x].rs].id=++IN,t[IN]=itn();
		add(tr[x].rs,mid+1,r,a,b,z);
	}
	addt(tr[x].id,1,n,a,b);
}
inline int query(int x,int l,int r,int a,int b,ll c){
	if(l==r)return l;
	int mid=(l+r)>>1;ll z=sch(tr[tr[x].rs].id,1,n,a,b);
	if(z>=c)return query(tr[x].rs,mid+1,r,a,b,c);
	else return query(tr[x].ls,l,mid,a,b,c-z);
}

signed main()
{
	n=read();
	tr[++NN].id=++IN;
	for(int M=read();M--;){
		int opt=read(),a=read(),b=read();ll c=read();
		if(opt==1)add(1,-n,n,a,b,c);
		else printf("%d\n",query(1,-n,n,a,b,c));
	}
	return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值