bzoj3110 K大数查询

bzoj3110 K大数查询

Description

有N个位置,M个操作。操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c
如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少。

Input

第一行N,M
接下来M行,每行形如1 a b c或2 a b c
Output输出每个询问的结果

Sample Input

2 5
1 1 2 1
1 1 2 2
2 1 1 2
2 1 1 1
2 1 2 3

Sample Output

1
2
1

HINT

样例说明:
第一个操作后位置 1 的数只有 1 , 位置 2 的数也只有 1 。
第二个操作 后位置 1的数有 1 、 2 ,位置 2 的数也有 1 、 2 。
第三次询问 位置 1 到位置 1 第 2 大的数 是1 。
第四次询问 位置 1 到位置 1 第 1 大的数是 2 。
第五次询问 位置 1 到位置 2 第 3大的数是 1 。
N,M<=50000,N,M<=50000
a<=b<=N
1操作中abs©<=N
2操作中c<=Maxlongint

解题思路:

百度一下,会发现大部分题解都是用树套树来做的,有的是用线段树套线段树,有的用树状数组套树状数组。但本题实际上也可以用二分的策略来解决。我们用二分+树状数组来解决该题。前置知识:二分、树状数组(区间修改)。

首先明晰目标:求l到r位置上所有数(每个位置上可能不止一个数)第c大的数。

我们假设所有操作(或询问,或插入)都在操作集合que中。而用一个长整型数组ans来存储答案,用一个num数组来存放所有要插入的c(num数组中所有元素唯一,即unique一下)。

每次递归我们取出一个中间数num[mid],如果当前操作que[i]是插入操作,我们判断其c是否大于等num[mid],若大于等于则对que[i]所对应的[l,r]区间所有位置+1。那么树状数组每个位置存放的就是该位置>num[mid]的元素的个数(为啥这样?因为我们求的是第k大的数,是降序!)。所以当查询时,若此时区间[l,r]内数的个数 >= 第k大(实际上是c,但为了区别就叫它c,下同)数,则说明num[mid]就可能是第k大嘛。(比num[mid]大的数假设有cnt个,而我们要求第k大的数,若 cnt >= k ,那么至少第k大的数包含在这cnt个数中) 那我们下面要做的就是缩小范围,直至确定第k大的数是谁。

所以我们分两部分递归,一部分是小于num[mid]的small派,另一部分是已经有粗略答案的big派。对于small派,我们目标是减小num[mid]来找寻其可能的答案;对于big派,我们目标是增大num[mid]来精确其答案。

代码示例:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const int MAXN = 60000;

struct Query{
	/*
	op为操作类型 
	l和r分别为左右边界
	c见题意
	cnt为二分时累加的值 
	aidx为ans数组的下标 
	*/ 
	int op,l,r,aidx;
	ll c,cnt;
	Query(){}
	Query(int op,int l,int r,ll c):op(op),l(l),r(r),c(c){
		cnt = 0;
	}
	bool operator < (const Query &B) const{
		return c < B.c;
	}
};
//下面三个数组分别是:操作集合、归并用临时数组1,临时数组2 
Query que[MAXN],tmp1[MAXN],tmp2[MAXN];
ll ans[MAXN];	//答案数组 
vector<ll> num;	//用来存不同的c,便于二分 

//下面是树状数组代码
ll st[2][MAXN];
void add(int k,int x,int y){
	for(;x < MAXN;x += x&-x) st[k][x] += y;
} 
ll ask(int k,int x){
	ll res = 0;
	for(;x;x -= x&-x) res += st[k][x];
	return res;
}
//将que中下标为id的元素l,r所有位置+1 
void superAdd(int id){ 
	add(0,que[id].l,1);
	add(0,que[id].r+1,-1); 
	add(1,que[id].l,que[id].l);
	add(1,que[id].r+1,-que[id].r-1);
}
//返回que中下标为id的[l,r]区间内元素个数 
ll superAsk(int id){
	ll res = (que[id].r+1)*ask(0,que[id].r) - ask(1,que[id].r);
	res -= (que[id].l)*ask(0,que[id].l-1) - ask(1,que[id].l-1);
	return res; 
} 
void clear(int id){
	add(0,que[id].l,-1);
	add(0,que[id].r+1,1); 
	add(1,que[id].l,-que[id].l);
	add(1,que[id].r+1,que[id].r+1);
}
//线段树区间修改代码到此结束 

void solve(int ql,int qr,int l,int r){
	/*
	其中ql、ql分别是操作集合que的左右端点,请注意,左闭右开
	l和r是num数组的左右端点,左闭右开 
	*/
	int mid = (l+r)/2;
	int bcnt = 0,scnt = 0;	
	//bigcnt,smallcnt,分别代表大于num[mid]的和小于num[mid]的元素 
	for(int i = ql;i < qr;i++){
		if(que[i].op == 1){
			if(que[i].c >= num[mid]) superAdd(i),tmp1[bcnt++] = que[i];
			else tmp2[scnt++] = que[i];
		}else{
			if(que[i].cnt + superAsk(i) >= que[i].c){
				ans[que[i].aidx] = num[mid];
				tmp1[bcnt++] = que[i];
			}else{
				que[i].cnt += superAsk(i);
				tmp2[scnt++] = que[i];
			} 
		}
	}
	for(int i = ql;i < qr;i++) 
	if(que[i].op == 1 && que[i].c >= num[mid])
		clear(i);
	for(int i = 0;i < scnt;i++) que[ql+i] = tmp2[i];
	for(int i = 0;i < bcnt;i++) que[ql+scnt+i] = tmp1[i];
	/*
	要在这里递归和判断是否满足条件,因为本次操作对下一次操作有影响,
	所以需要先操作,再递归;而防止死循环需要如下判断条件 
	*/ 
	if(ql >= qr-1 || l >= r-1) return; 
	solve(ql,ql+scnt,l,mid);
	//大的集合也要递归,因为可能答案会小于num[mid]呢,相当不断逼近答案 
	solve(ql+scnt,qr,mid,r);
} 

int main(){
	int n,m;
	int acnt = 0;	//答案数组的大小 
	scanf("%d%d",&n,&m);
	for(int i = 0;i < m;i++){
		int op,l,r;
		ll c;
		scanf("%d%d%d%lld",&op,&l,&r,&c);
		que[i] = Query(op,l,r,c);
		if(op == 1) num.push_back(c);
		else que[i].aidx = acnt++;
	}
	sort(num.begin(),num.end());
	int sz = unique(num.begin(),num.end()) - num.begin();
	solve(0,m,0,sz);	//左闭右开 
	for(int i = 0;i < acnt;i++)
		printf("%lld\n",ans[i]); 
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

迷亭1213

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值