【BZOJ 3110】 [Zjoi2013]K大数查询 整体二分+树状数组区间修改

额,只能说整体二分是一个很神奇的东西,首先既然是二分虽然加了一个整体听起来变得立马高大上起来,但是还是需要从最基本的二分思路出发才能理解。首先如果对于只有一次地查询操作的话我们很容易就可以想到二分的写法,二分权值然后加上cheak操作判断比当前大的值有多少个,然后不断缩小二分的区间直到最后l==r。类似的,在整体二分我们同样用到类似的思想,这里简单模拟一下操作过程,希望能帮组大家理解;

首先用一个p的结构体记录下所有的操作,包括类型区间及k。然后由于题目是查询第k大的,插入的数也是有可能会出现负数,所以每一个插入的操作,插入的k变成n+1-k,然后再把查询的k大变成第k小就可以巧妙的回避负数的情况。

离线处理以后就开始二分,同样是二分权值,然后对于每一个权值区间都会有且仅仅只会对这部分区间的答案造成贡献的操作(一定要好好理解这里,这里就是算法的关键所在)。例如现在我二分出了一个区间是l->mid而现在有插入操作是插入一个大于mid的值,自然无论如何对于l->mid区间是不会有贡献的所以就丢到mid+1->r区间去(类似于一个排序)。而如果对于一个查询操作,查询第5小的数是多少,而l->mid之间的值有7个那么这个答案一定不会在这个区间内同样的扔到右边去。

因此我们需要有一个能快速查询一段区间内有多少个数的数据结构,就是区间修改区间查询可以使用线段树,但是我用的树状数组所以可能不好理解,你就当做区间修改区间查询就好了


#include
#include
#include
#define maxn 100020
#define LL long long
using namespace std;
int n,m,ans[maxn];
LL c1[maxn],c2[maxn];
struct que{
	int id,pos,l,r;
	LL k;
}p[maxn],p1[maxn],p2[maxn];
void update(int x,LL add){
	for(int i=x;i<=n;i+=i&(-i)){
		c1[i]+=add;
		c2[i]+=x*add;
	}
}
LL query(int x){
	LL ans=0;
	for(int i=x;i>0;i-=i&(-i)){
		ans+=(x+1)*c1[i]-c2[i];
	}
	return ans;
}

void solve(int s,int t,LL l,LL r){
	if(s>t)return;
	if(l==r){
		for(int i=s;i<=t;i++)
			if(p[i].pos==2){
				ans[p[i].id]=l;
			}
		return;
	}LL mid=l+r>>1;//二分答案 
	int s1=0,s2=0;
	for(int i=s;i<=t;i++){//枚举操作 当前操作依旧是按照顺序进行的 
		if(p[i].pos==1){//插入 
			if(p[i].k<=mid){//加入的数小于二分出来的权值
				update(p[i].l,1);
				update(p[i].r+1,-1);
				p1[++s1]=p[i];//放进第一个队列
			}else {
				p2[++s2]=p[i];
			}
		}else{
			//查询l到r之间一共有多少个数,由于上面说到的,这些数一定小于等于mid 
			LL tmp=query(p[i].r)-query(p[i].l-1);
			if(tmp>=p[i].k)p1[++s1]=p[i];
			else p[i].k-=tmp,p2[++s2]=p[i];//减去比他已经小了的然后放进第二个队列 
		}
	}
	//将树状数组清空 
	for(int i=s;i<=t;i++){
		if(p[i].pos==1&&p[i].k<=mid){
			update(p[i].l,-1);
			update(p[i].r+1,1);
		}
	}
	bool f1=false,f2=false;
	for(int i=1;i<=s1;i++)p[i+s-1]=p1[i],f1=true;
	for(int i=1;i<=s2;i++)p[i+s+s1-1]=p2[i],f2=true;
	if(f1)solve(s,s+s1-1,l,mid);
	if(f2)solve(s+s1,t,mid+1,r);
}

int main(){
	scanf("%d%d",&n,&m);
	int cnt=0;
	for(int i=1;i<=m;i++){
		scanf("%d%d%d%lld",&p[i].pos,&p[i].l,&p[i].r,&p[i].k);
		if(p[i].pos==1){
			p[i].k=n+1-p[i].k;
		}else p[i].id=++cnt;
	}
	solve(1,m,0,(LL)n*2+2);
	for(int i=1;i<=cnt;i++)printf("%d\n",(LL)n+1-ans[i]);
	return 0;
}


你可能感兴趣的:(ac之路,bzoj,数据结构)