整體二分學習筆記

liuchanglc發表於2021-03-12

用途

在資訊學競賽中,有一部分題可以使用二分的辦法來解決。

但是當這種題目有多次詢問且每次詢問我們對每個查詢都直接二分,可能會收穫一個 \(TLE\)

這時候我們就會用到整體二分。

整體二分的主體思路就是把多個查詢一起解決。(所以這是一個離線演算法)

可以使用整體二分解決的題目需要滿足以下性質:

\(1\)、詢問的答案具有可二分性

\(2\)、修改對判定答案的貢獻互相獨立,修改之間互不影響效果

\(3\)、修改如果對判定答案有貢獻,則貢獻為一確定的與判定標準無關的值

\(4\)、貢獻滿足交換律,結合律,具有可加性

\(5\)、題目允許使用離線演算法

實現

P3332 [ZJOI2013]K大數查詢 為例

暴力的做法就是二分一個權值,檢視大於等於這個權值的數是不是大於等於 \(k\) 個。

如果是,就把這個權值增大,否則就去縮小這個權值。

整體二分需要實現一個函式 \(solve(al,ar,bl,br)\)

表示對於當前詢問的區間 \([al,ar]\),二分的權值在 \([bl,br]\) 範圍中。

\(mids=(bl+br)/2\),我們去遍歷 \([al,ar]\) 中的所有操作。

如果是修改操作並且加入的權值在 \([mids+1,br]\) 中,把對應的區間 \([l,r]\)整體加一。

如果是詢問操作,去查詢 \([l,r]\) 中有多少數,如果有大於等於 \(k\) 個,我們就把這個詢問放到遞迴的右區間中,否則就放到左區間中。

當二分權值的左右端點相等時就去更新答案。

最後不要忘了把加入的貢獻清除。

程式碼

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#define rg register
template<typename T>void read(T &x){
	x=0;rg int fh=1;
	rg char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	x*=fh;
}
const int maxn=2e5+5;
struct trr{
	int l,r,laz,siz;
	long long sum;
}tr[maxn];
void push_up(rg int da){
	tr[da].sum=tr[da<<1].sum+tr[da<<1|1].sum;
}
void push_down(rg int da){
	if(!tr[da].laz) return;
	tr[da<<1].laz+=tr[da].laz;
	tr[da<<1|1].laz+=tr[da].laz;
	tr[da<<1].sum+=1LL*tr[da<<1].siz*tr[da].laz;
	tr[da<<1|1].sum+=1LL*tr[da<<1|1].siz*tr[da].laz;
	tr[da].laz=0;
}
void build(rg int da,rg int l,rg int r){
	tr[da].l=l,tr[da].r=r,tr[da].siz=r-l+1;
	if(l==r) return;
	rg int mids=(l+r)>>1;
	build(da<<1,l,mids),build(da<<1|1,mids+1,r);
}
void xg(rg int da,rg int l,rg int r,rg int val){
	if(tr[da].l>=l && tr[da].r<=r){
		tr[da].laz+=val;
		tr[da].sum+=tr[da].siz*val;
		return;
	}
	push_down(da);
	rg int mids=(tr[da].l+tr[da].r)>>1;
	if(l<=mids) xg(da<<1,l,r,val);
	if(r>mids) xg(da<<1|1,l,r,val);
	push_up(da);
}
long long cx(rg int da,rg int l,rg int r){
	if(tr[da].l>=l && tr[da].r<=r) return tr[da].sum;
	push_down(da);
	rg int mids=(tr[da].l+tr[da].r)>>1;
	rg long long nans=0;
	if(l<=mids) nans+=cx(da<<1,l,r);
	if(r>mids) nans+=cx(da<<1|1,l,r);
	return nans;
}
int n,m,ans[maxn],id[maxn];
struct asd{
	int op,l,r;
	long long val;
	asd(){}
	asd(rg int aa,rg int bb,rg int cc,rg long long dd){
		op=aa,l=bb,r=cc,val=dd;
	}
}b[maxn];
int tmp1[maxn],tmp2[maxn];
void solve(rg int al,rg int ar,rg int bl,rg int br){
	if(bl==br){
		for(rg int i=al;i<=ar;i++) ans[id[i]]=bl;
		return;
	}
	rg int mids=(bl+br)>>1,now1=0,now2=0;
	for(rg int i=al;i<=ar;i++){
		if(b[id[i]].op==1){
			if(b[id[i]].val>mids){
				tmp2[++now2]=id[i];
				xg(1,b[id[i]].l,b[id[i]].r,1);
			} else {
				tmp1[++now1]=id[i];
			}
		} else {
			rg long long tmp=cx(1,b[id[i]].l,b[id[i]].r);
			if(b[id[i]].val<=tmp){
				tmp2[++now2]=id[i];
			} else {
				b[id[i]].val-=tmp;
				tmp1[++now1]=id[i];
			}
		}
	}
	for(rg int i=al;i<=ar;i++){
		if(b[id[i]].op==1){
			if(b[id[i]].val>mids){
				xg(1,b[id[i]].l,b[id[i]].r,-1);
			} 
		}
	}
	for(rg int i=1;i<=now1;i++) id[al+i-1]=tmp1[i];
	for(rg int i=1;i<=now2;i++) id[al+now1+i-1]=tmp2[i];
	solve(al,al+now1-1,bl,mids);
	solve(al+now1,ar,mids+1,br);
}
int main(){
	read(n),read(m);
	rg int aa,bb,cc;
	rg long long dd;
	for(rg int i=1;i<=m;i++){
		read(aa),read(bb),read(cc),read(dd);
		b[i]=asd(aa,bb,cc,dd);
	}
	for(rg int i=1;i<=m;i++) id[i]=i;
	build(1,1,n);
	solve(1,m,-n,n);
	for(rg int i=1;i<=m;i++){
		if(b[i].op==2) printf("%d\n",ans[i]);
	}
	return 0;
}

相關文章