整体二分-学习笔记


作者会把自己学习时遇到的一些疑问回答,尽量写的详细、合理,让更多人能够理解这个算法。

简介

整体二分作为一种离线算法,作用其实并不是很广泛。但是它能够以较短的码量、较低的思维难度解决一些其他算法无法或者难以解决的问题,确实不失为一种优秀的算法。更重要的是算法思想:统一解决问题,或者换句话说,全部输入,离线一次性解决一切。这种思想确实可以拓展到更多内容上。

内容

顾名思义,整体二分是处理这样一种问题:可以使用二分法解决,但是对于每一个询问都进行一次二分时间复杂度无法接受的问题。这时候,整体二分就诞生了:它先将所有操作读入,然后进行一个统一的二分,或者说:分治。因为是一次性处理所有询问、修改,因此这种算法被称为:整体二分

先回忆一下普通二分的内容:当询问和操作满足单调性时,每次查找一个中间点与当前询问作比较,决定是接着往哪个方向寻找。这样子区间长度每次减半,时间复杂度是优秀的 \(O(\log_2n)\)

拓展到整体二分上,我们相当于是同时对每一个询问查找。每一次二分,我们会有一个值域区间,一个当前需要处理的操作序列。我们接着来比较每个操作以及中点 mid,也就是说,对于当前的值域区间,所有询问操作会被分成两个部分:目标在左区间的,以及目标在右区间的。而为了接着处理这些询问,我们相当于是要接着分别处理两个区间的询问,一直递归下去。当然,如果左区间或者右区间没有操作序列,那自然就不需要接着操作了,直接返回即可。如果当前值域只剩下一个点了,也就是我们已经递归到终点了,直接存下答案即可。

具体实现的话,我们需要一个 \(solve\) 函数,有四个参数:\(ql,qr,l,r\) ,分别表示当前操作序列的左右端点,以及当前值域的左右端点。操作序列之所以会有一个区间,是因为处理操作时会把当前区间分成两部分,为了方便就直接在当前区间分开,再递归处理下去。

应该能够理解的吧QAQ

例题

P3834 【模板】可持久化线段树 2

算是模板题吧。对于这种多个询问并且询问满足单调性(第 k 小)的题目,就很适合整体二分。(其实带修也能做。具体来说,就是我们把原数列每一个位置当作一次修改,后面的就是查询。处理一个询问的时候,我们想要得出一个区间的第 k 小值与当前 mid 的关系,最常见的方法就是查询当前区间小于等于 mid 的数的个数,与 k 比一下大小。具体可以用树状数组实现。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
#define lb(x) (x&-(x))
#define mid (l+r>>1)
#define ll long long
const int N=2e5+2,M=1002,I=0x3f3f3f3f;
int read()
{
	int x=0,y=1;
	char c=getchar();
	for(;!isdigit(c);c=getchar()) if(c=='-') y=0;
	for(;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^48);
	return y?x:-x;
}
int n,m,cnt;
int ans[N];
struct _N
{
	int x,y,k,id,type;
}a[N<<1],b1[N<<1],b2[N<<1];
int c[N];
void add(int x,int y)
{
	for(;xqr) return;
	if(l==r)
	{
		for(int i=ql;i<=qr;i++) if(a[i].type==2) ans[a[i].id]=l;
		return;
	}
	int p1=0,p2=0;
	for(int i=ql;i<=qr;i++)
	{
		if(a[i].type==1)
		{
			if(a[i].x<=mid) add(a[i].id,1),b1[++p1]=a[i];
			else b2[++p2]=a[i];
		}
		if(a[i].type==2)
		{
			int t=get(a[i].y)-get(a[i].x-1);
			if(t>=a[i].k) b1[++p1]=a[i];
			else b2[++p2]=a[i],b2[p2].k-=t;
		}
	}
	for(int i=ql;i<=qr;i++) if(a[i].type==1&&a[i].x<=mid) add(a[i].id,-1);
	for(int i=1;i<=p1;i++) a[ql+i-1]=b1[i];
	for(int i=1;i<=p2;i++) a[ql+p1+i-1]=b2[i];
	solve(ql,ql+p1-1,l,mid);
	solve(ql+p1,qr,mid+1,r);
}
int main()
{
	n=read(),m=read();
	for(int i=1;i<=n;i++)
	{
		int x=read();
		a[++cnt]=(_N){x,0,0,i,1};
	}
	for(int i=1;i<=m;i++)
	{
		int l=read(),r=read(),k=read();
		a[++cnt]=(_N){l,r,k,i,2};
	}
	solve(1,cnt,-I,I);
	for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
	return 0;
}

算是讲完了吧QAQ

以后如果有时间会讲的更详细,也会放一些例题。现在就先咕咕咕了。

这篇随笔更多的是自己的学习体会,自己的学习笔记,可能写的不是很好,也不要介意。