整体二分总结

趁热打铁·整体二分总结

例题:

有数组 \(a[1...n]\) ,回答m个问题:\(Q(i,j,k)\):区间 \(a[i...j]\) 从小到大排序后第k个数是什么。

\(n<100000,m<5000\)

思路

没有中途的修改,可以使用离线思想。

把询问和更改(初始赋值)搞到一起,一起进行二分,具体如下:

  • doo(ll ql,ll qr,ll L,ll R)\([ql,qr]\)的询问答案在\([L,R]\)之间

  • mid=(L+R)>>1

  • \(ql\)枚举到\(qr\)

  • 若是修改操作,若q[i].x<=mid的话,将其压到线段树里,归并在左区间。否则归并到右区间。

  • 否则,查询线段树,如果<=mid的数大于k个的话,将其归并到左区间,否则归并到右区间。

  • 将左右区间归位。

  • 清空线段树(只需要清空左区间,因为只有左区间才会压进去)。

  • 二分下去。

code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll n,m,a[1000000],ans[1000000],tr[1000000];
struct nood{
	ll x,y,k,tp,wz;
};
nood q[1000000],ql[1000000],qr[1000000];
int lowbit(int x){
	return x&(-x);
}
void add(ll x,ll y){
	for(int i=x;i<=n;i+=lowbit(i)){
		tr[i]+=y;
	}
}
int getsum(ll x){
	ll res=0;
	for(int i=x;i>0;i-=lowbit(i)){
		res+=tr[i];
	}
	return res;
}
void doo(ll l,ll r,ll L,ll R){
	if(l>r){
		return;
	}
	if(L==R){
		for(int i=l;i<=r;i++){
			if(q[i].tp==2){
				ans[q[i].wz]=R;
			}
		}
		return;
	}
	int mid=(L+R)>>1,t1=0,t2=0;
	for(int i=l;i<=r;i++){
		if(q[i].tp==1){//修改 
			if(q[i].x<=mid){//将其压到线段树里,归并在左区间
				add(q[i].wz,1);
				ql[++t1]=q[i];
			}
			else{//归并到右区间
				qr[++t2]=q[i];
			}
		}
		else{
			int lss=getsum(q[i].y)-getsum(q[i].x-1);
			if(lss>=q[i].k){//归并到左区间
				ql[++t1]=q[i];
			}
			else{//归并到右区间
				q[i].k-=lss;
				qr[++t2]=q[i];
			}
		}
	}
	for(int i=1;i<=t1;i++)//清空线段树
		if(ql[i].tp==1){
			add(ql[i].wz,-1);
		}
	//将左右区间归位。
	for(int i=1;i<=t1;i++){
		q[l+i-1]=ql[i];
	}
	for(int i=1;i<=t2;i++){
		q[l+t1+i-1]=qr[i];
	}
	//二分下去
	doo(l,l+t1-1,L,mid);
	doo(l+t1,r,mid+1,R);
}
int main(){
	cin>>n>>m;
	ll tot=0;
	ll x,y,k;
	for(int i=1;i<=n;i++){
		cin>>x;
		q[++tot].x=x,q[tot].tp=1,q[tot].wz=i;
	}
	for(int i=1;i<=m;i++){
		cin>>x>>y>>k;
		q[++tot].x=x,q[tot].tp=2,q[tot].wz=i,q[tot].y=y,q[tot].k=k;
	}
	doo(1,tot,-1e9,1e9);
	for(int i=1;i<=m;i++){
		cout<<ans[i]<<"\n";
	}
}
posted @ 2025-05-27 21:29  MistyPost  阅读(15)  评论(3)    收藏  举报