整體二分


還是把luogu上那篇搬過來吧qwq


何為整體二分?二分她兒子

先來看道題吧:

靜態區間第\(K\)小:

給一個長度為\(n\)的序列\(a\)\(m\)次詢問,每次詢問用一個三元組表示\((ql,qr,k)\),即\(a_{ql} ... a_{qr}\)中第\(k\)小的數是多少。(不一定要在線)

某神犇:主席樹板子題,三分鍾切了!

\(Orz\)。。。

因為我太菜了,所以不會主席樹\(qwq\)。。。

那我們怎么辦呢?

整體二分就橫空出世了。。。

怎么個二分法呢? 

我們先把所有詢問放到一起。

然后二分一個\(mid\)(數值),同時令當前二分到的區間為\([l,r]\)。。。

假設我們有左右兩個用於放詢問的空籃子(大霧

對於一組詢問\((ql,qr,k)\),若\(a_{ql} ... a_{qr}\)中比\(mid\)小的數大於或等於\(k\),那么這個詢問的答案一定在\([l,mid]\)里,我們就把他扔進左邊的籃子里。

反之我們就把他扔進右邊的籃子里。

這樣就把詢問分成了左右兩種,同時二分的區間也變成了\([l,mid]\)\([mid+1,r]\),我們就可以遞歸做下去了。。。結束條件是\(l=r\),則這些詢問的答案都是\(l\)

是不是ylmb?

那么序列\(a\)里的數要怎么處理呢?

我們也把他看做一個二元組\((i,x)\)表示\(a_i = x\)和詢問放在一起。。。

那么在二分到\(mid\)的時候若有一個\(a_i>mid\),那么它和答案區間\([l,mid]\)是半毛錢關系都沒有的,我們就把他扔進\([mid+1,r]\)里。

反之我們把他扔進左邊的籃子。

那么怎么維護區間內比\(mid\)小的樹呢?樹狀數組辣。。。做個前綴和就好了。當遇到了\((i,x)\)時,如果\(x<=mid\),就在位置\(i\)上加\(1\)

記住結束后要及時清空樹狀數組(盡量不要用\(memset\),如果數組很大的話\(memset\)還不如\(for\)循環)

時間復雜度:\(O(nlog^2n)\) (共遞歸\(logn\)層,一層的復雜度是\(O(nlogn)\)的)

估計又是ylmb

還是看例題吧。。。

P3834 【模板】可持久化線段樹 1(主席樹)

就是上面的例子吧。。。

主席樹模板題怎么能用主席樹做呢?

具體實現(還是一個世紀前的碼風):

#include <bits/stdc++.h>
#define IO(file) freopen(file".in","r",stdin),freopen(file".out","w",stdout)
#define debug(...) fprintf(stderr,__VA_ARGS__)
#define getchar nc
#define N 500010
#define INF 1e9
using namespace std;
inline char nc()
{
	static char buf[1<<10],*p1=buf,*p2=buf;
	return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<10,stdin),p1==p2)?EOF:*p1++;
}
inline int read()
{
	register int x=0,f=1; register char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f*=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	return x*f;
}
template<class T> inline void write(T x,char end='\n')
{
	if(x==0) putchar('0'); if(x<0) x=-x,putchar('-');
	static char buf[256]; register int top=0;
	while(x) buf[++top]=x%10+48,x/=10;
	while(top) putchar(buf[top--]);
	putchar(end);
}
int n,m,cnt;
struct Query
{
	int x,y,k;
	int pos,type;
	Query(){}
	Query(int i,int j,int kk,int p,int t):x(i),y(j),k(kk),pos(p),type(t){}	
}q[N],q1[N],q2[N];
int ans[N],c[N];
inline int lowbit(int x){return x&-x;}
inline void add(int x,int v)
{
	for(;x<=n;x+=lowbit(x))
		c[x]+=v;
}
inline int sum(int x)
{
	int res=0;
	for(;x;x-=lowbit(x))
		res+=c[x];
	return res;
}
void solve(int l,int r,int ql,int qr)
{
	if(l>r || ql>qr) return;
	if(l==r)
	{
		for(int i=ql;i<=qr;i++)
			if(q[i].type) ans[q[i].pos]=l;
		return;
	}
	int cnt1=0,cnt2=0,mid=(l+r)>>1;
	for(int i=ql;i<=qr;i++)
	{
		if(q[i].type==0)
		{
			if(q[i].x<=mid) add(q[i].pos,q[i].k),q1[++cnt1]=q[i];
			else q2[++cnt2]=q[i];
		}
		else
		{
			int tmp=sum(q[i].y)-sum(q[i].x-1);
			if(q[i].k<=tmp) q1[++cnt1]=q[i];
			else q[i].k-=tmp,q2[++cnt2]=q[i];
		}
	}
	for(int i=1;i<=cnt1;i++)
		if(q1[i].type==0) add(q1[i].pos,-q1[i].k);
	for(int i=1;i<=cnt1;i++)
		q[ql+i-1]=q1[i];
	for(int i=1;i<=cnt2;i++)
		q[ql+cnt1+i-1]=q2[i];
	solve(l,mid,ql,ql+cnt1-1);
	solve(mid+1,r,ql+cnt1,qr);
}
int main()
{
#ifdef LOCAL
	IO("data");
#endif
	n=read(),m=read();
	for(int i=1;i<=n;i++)
	{
		int x=read();
		q[++cnt]=Query(x,0,1,i,0);
	}
	for(int i=1;i<=m;i++)
	{
		int l=read(),r=read(),k=read();
		q[++cnt]=Query(l,r,k,i,1);
	}
	solve(-INF,INF,1,cnt);
	for(int i=1;i<=m;i++)
		write(ans[i]);
	return 0;
}

時間復雜度\(O(nlog^2n)\),好像比主席樹\(O(nlogn)\)慢一點。。。

實測下來:

主席樹(\(804ms\)

主席樹

整體二分(\(1.28s\)

整體二分

好像是慢一點。。。

P2617 Dynamic Rankings

動態區間第\(k\)小。

看起來逼格很高的樣子。。。

也有兩種做法。。。

一種樹狀數組套主席樹(這里不講)

還有整體二分

兩者的復雜度都是\(O(nlog^2n)\)

講一下整體二分的做法。

把原數組里每個數看做\((i,a_i,1)\),表示如果\(a_i<=mid\)的話則位置\(i\)加一。

那么就可以很顯然的把點修改看做兩個這樣的三元組\((i,a_i,-1)\)\((i,t,1)\),這樣就可以解決修改操作了。

區間詢問和上面類似,按順序塞進\(q\)數組里,把原序列放在前面就好了。。。

#include <bits/stdc++.h>
#define getchar nc
#define debug(...) fprintf(stderr,__VA_ARGS__)
#define IO(file) freopen(file".in","r",stdin),freopen(file".out","w",stdout)
#define N 500010
#define INF 1e9
using namespace std;
inline char nc()
{
	static char buf[1<<10],*p1=buf,*p2=buf;
	return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<10,stdin),p1==p2)?EOF:*p1++;
}
inline int read()
{
	register int x=0,f=1; register char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f*=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	return x*f;
}
template<class T> inline void write(T x,char end='\n')
{
	if(x==0) putchar('0'); if(x<0) x=-x,putchar('-');
	static char buf[256]; register int top=0;
	while(x) buf[++top]=x%10+48,x/=10;
	while(top) putchar(buf[top--]);
	putchar(end);
}
int n,m,cnt;
struct Query
{
	int x,y,k;
	int pos,type;
	Query(){}
	Query(int i,int j,int kk,int p,int t):x(i),y(j),k(kk),pos(p),type(t){}
}q[N],q1[N],q2[N];
int ans[N],c[N];
inline int lowbit(int x){return x&-x;}
inline void add(int x,int v)
{
	for(;x<=n;x+=lowbit(x)) c[x]+=v;
}
inline int sum(int x)
{
	int res=0;
	for(;x;x-=lowbit(x)) res+=c[x];
	return res;
}
void solve(int l,int r,int ql,int qr)
{
	if(l>r||ql>qr) return;
	if(l==r)
	{
		for(int i=ql;i<=qr;i++)
			if(q[i].type) ans[q[i].pos]=l;
		return;
	}
	int cnt1=0,cnt2=0;
	int mid=(l+r)>>1;
	for(int i=ql;i<=qr;i++)
		if(q[i].type)
		{
			int tmp=sum(q[i].y)-sum(q[i].x-1);
			if(q[i].k<=tmp) q1[++cnt1]=q[i];
			else q[i].k-=tmp,q2[++cnt2]=q[i];
		}
		else
		{
			if(q[i].x<=mid) add(q[i].pos,q[i].k),q1[++cnt1]=q[i];
			else q2[++cnt2]=q[i];
		}
	for(int i=1;i<=cnt1;i++)
		if(q1[i].type==0) add(q1[i].pos,-q1[i].k);
	for(int i=1;i<=cnt1;i++) q[ql+i-1]=q1[i];
	for(int i=1;i<=cnt2;i++) q[ql+cnt1+i-1]=q2[i];
	solve(l,mid,ql,ql+cnt1-1);
	solve(mid+1,r,ql+cnt1,qr);
}
int a[N];
int main()
{
	n=read(),m=read();
	for(int i=1;i<=n;i++)
	{
		a[i]=read();
		q[++cnt]=Query(a[i],0,1,i,0);
	}
	int tot=0;
	for(int i=1;i<=m;i++)
	{
		char ch=getchar();while(ch!='Q'&&ch!='C')ch=getchar();
		int x=read(),y=read(),z;
		if(ch=='C') q[++cnt]=Query(a[x],0,-1,x,0),q[++cnt]=Query(a[x]=y,0,1,x,0);
		else z=read(),q[++cnt]=Query(x,y,z,++tot,1);
	}
	solve(-INF,INF,1,cnt);
	for(int i=1;i<=tot;i++)
		write(ans[i]);
	return 0;
}

和樹狀數組套主席樹的對比:

整體二分:

233

跑了\(2.33s\) 【滑稽】。

樹狀數組套主席樹:

15s

\(15.61s\),空間和時間都被整體二分吊起來錘。。。

可見整體二分的優勢。。。

P3332 [ZJOI2013]K大數查詢

一道毒瘤題。。。(把題號倒過來)

題意有點鬼畜。。。建議看一下樣例說明。。。

注意是第\(k\)大不是第\(k\)小。。。

點修改變成了區間修改。。。怎么辦?

把樹狀數組換成線段樹就好了。。。

注意比\(mid\)小的數的個數會爆\(int\)。。。 害我調了一上午

#include <bits/stdc++.h>
using namespace std;
#define debug(...) fprintf(stderr,__VA_ARGS__)
typedef long long ll;
const int MAXN=200010;
const ll INF=2e18;
struct seg{
	int l,r;
	ll add,sum;
}t[MAXN<<2];
void pushup(int x){
	t[x].sum=t[x<<1].sum+t[x<<1|1].sum;
}
void pushdown(int x){
	if (!t[x].add) return;
	int l=t[x].l,r=t[x].r,mid=(l+r)>>1;
	t[x<<1].add+=t[x].add;
	t[x<<1|1].add+=t[x].add;
	t[x<<1].sum+=t[x].add*(mid-l+1);
	t[x<<1|1].sum+=t[x].add*(r-mid);
	t[x].add=0;
}
void build(int x,int l,int r){
	t[x]=(seg){l,r,0,0};
	if (l==r)
		return;
	int mid=(l+r)>>1;
	build(x<<1,l,mid);
	build(x<<1|1,mid+1,r);
	pushup(x);
}
void update(int x,int ql,int qr,ll v){
	int l=t[x].l,r=t[x].r;
	if (ql<=l&&r<=qr){
		t[x].add+=v;
		t[x].sum+=v*(r-l+1);
		return;
	}
	pushdown(x);
	int mid=(l+r)>>1;
	if (ql<=mid) update(x<<1,ql,qr,v);
	if (mid<qr) update(x<<1|1,ql,qr,v);
	pushup(x);
}
ll query(int x,int ql,int qr){
	int l=t[x].l,r=t[x].r;
	if (ql<=l&&r<=qr) return t[x].sum;
	pushdown(x);
	int mid=(l+r)>>1;ll res=0;
	if (ql<=mid) res+=query(x<<1,ql,qr);
	if (mid<qr) res+=query(x<<1|1,ql,qr);
	return res;
}
ll ans[MAXN];
int n,m;
struct event{
	int opt,x,y;ll v;int id;
	void print(){
		debug("%d %d %d %lld\n",opt,x,y,v);
	}
}q[MAXN],q1[MAXN],q2[MAXN];
void solve(ll l,ll r,int ql,int qr){
	if (ql>qr||l>r) return;
	if (l==r){
		for (int i=ql;i<=qr;i++)
			if (q[i].opt==2) ans[q[i].id]=l;
		return;
	}
	ll mid=(l+r)>>1;
	int cnt1=0,cnt2=0;
	for (int i=ql;i<=qr;i++){
		if (q[i].opt==1){
			if (q[i].v>mid){
				update(1,q[i].x,q[i].y,1);
				q1[++cnt1]=q[i];
			}else
				q2[++cnt2]=q[i];
		}else{
			ll tmp=query(1,q[i].x,q[i].y);
			if (tmp>=q[i].v)
				q1[++cnt1]=q[i];
			else{
				q[i].v-=tmp;
				q2[++cnt2]=q[i];
			}
		}
	}
	for (int i=1;i<=cnt1;i++)
		if (q1[i].opt==1&&q1[i].v>mid) update(1,q1[i].x,q1[i].y,-1);
	for (int i=ql;i<ql+cnt1;i++)
		q[i]=q1[i-ql+1];
	for (int i=ql+cnt1;i<=qr;i++)
		q[i]=q2[i-ql-cnt1+1];
	solve(mid+1,r,ql,ql+cnt1-1);
	solve(l,mid,ql+cnt1,qr);
}
int main(){
	scanf("%d%d",&n,&m);
	build(1,1,n);
	int tot=0;
	for (int i=1;i<=m;i++){
		int opt,a,b;ll c;
		scanf("%d%d%d%lld",&opt,&a,&b,&c);
		q[i]=(event){opt,a,b,c,opt==2?++tot:0};
	}
	solve(-n,n,1,m);
	for (int i=1;i<=tot;i++)
		printf("%lld\n",ans[i]);
	return 0;
}

好像有線段樹套平衡樹的做法。。。但復雜度就變成了三個\(log\)了。。。(二分一個\(log\),樹套樹的單個查詢\(2\)個),沒寫。。。不會

從上面三個例題來看,整體二分有以下幾個優勢:

  • 時空復雜度都比樹套樹優越;

  • 對比樹套樹代碼的復雜度簡單一點;

  • 蒟蒻專屬

但它好像只支持離線。。。這個就自求多福吧\(qwq\)。。。

整體二分大法吼啊!

PS:整體二分的復雜度\(O(nlog^2n)\),就看做是\(O(log\)值域大小\(*logn*n)\)吧。。。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM