1. 程式人生 > 實用技巧 >整體二分

整體二分

整體二分

今天剛透徹了整體二分,寫篇blog;
由於整體二分比較難以口述,來道例題講解
靜態區間第k小
同時也是主席樹模板題,可是主席樹模板怎麼能用主席樹寫呢
往下看,不會主席樹的也可以水過這道紫體題了;
首先設A序列表示表示給定序列;
所有詢問的答案一定在MINA~MAXA之間;
對MINA~MAXA進行二分答案;
二分答案一個詢問想必大家都會,單數這道題時間不允許;
考慮對整體進行二分,對於所有詢問二分;
也就是對詢問進行分類,考慮二分出的答案mid;
用樹狀陣列維護小於等於mid的個數;
對於每個區間ask(r)-ask(l-1)可以知道在這個詢問區間裡小於等於mid的個數;
就可以把所有詢問分為兩類,設num為l~r裡小於等於mid的個數;
第一類:num<=k 第二類num>k;
考慮第一類詢問的答案一定在L~mid之間;
第二類詢問的答案一定在mid+1~R之間;
L,R表示當前的答案區間;
那麼就可以把詢問分成兩類在分別將行分治
知道L==R為止,L就是答案;
注意A序列也要分開;
考慮對於第一類詢問,A序列中>mid的值對這些區間沒有影響
所以可以直接捨棄;
對於第二類詢問,它們的答案一定在mid+1~r之間
所以只考慮>mid的值;

#include<iostream>
#include<cstdio>
using namespace std;
const int N=2e5+7;
const int inf=1e9; 
struct node{
	int op,x,y,z;
}q[N<<1],ql[N<<1],qr[N<<1];
int n,m,cnt;
int ans[N],t[N];
int lowbit(int x){
	return x&-x;
}
void change(int x,int val){
	for(;x<=N-7;x+=lowbit(x)){
		t[x]+=val;
	}
}
int ask(int x){
	int res=0;
	for(;x;x-=lowbit(x)){
		res+=t[x];
	}
	return res;
}
void work(int L,int R,int l,int r){
	if(l>r) return;
	if(L==R){
		for(int i=l;i<=r;i++){
			if(q[i].op>0) ans[q[i].op]=L;
		}
		return;
	}
	int mid=(L+R)>>1;
	int tl=0,tr=0;
	for(int i=l;i<=r;i++){
		if(q[i].op==0){//屬於A序列
			if(q[i].y<=mid) change(q[i].x,1),ql[++tl]=q[i];
			else qr[++tr]=q[i];
		} else {//屬於詢問區間
			int tt=ask(q[i].y)-ask(q[i].x-1);
			if(tt>=q[i].z) ql[++tl]=q[i];
			else {
				q[i].z-=tt;//減去tt;
				qr[++tr]=q[i];
			}
 		}
	}
	for(int i=r;i>=l;i--){
		if(q[i].op==0&&q[i].y<=mid) change(q[i].x,-1);//清空樹狀陣列;
	}
	for(int i=1;i<=tl;i++) q[l+i-1]=ql[i];
	for(int i=1;i<=tr;i++) q[l+tl+i-1]=qr[i];
	work(L,mid,l,l+tl-1);
	work(mid+1,R,l+tl,r);
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&q[++cnt].y);
		q[cnt].op=0;q[cnt].x=i;//op分類
	}
	for(int i=1;i<=m;i++){
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		q[++cnt].x=x;
		q[cnt].y=y;
		q[cnt].z=z;
		q[cnt].op=i;
	}
	work(-inf,inf,1,cnt);
	for(int i=1;i<=m;i++) cout<<ans[i]<<"\n";
}