• 整体二分


    还是把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='
    ')
    {
    	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='
    ')
    {
    	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
    ",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
    ",ans[i]);
    	return 0;
    }
    

    好像有线段树套平衡树的做法。。。但复杂度就变成了三个(log)了。。。(二分一个(log),树套树的单个查询(2)个),没写。。。不会

    从上面三个例题来看,整体二分有以下几个优势:

    • 时空复杂度都比树套树优越;

    • 对比树套树代码的复杂度简单一点;

    • 蒟蒻专属

    但它好像只支持离线。。。这个就自求多福吧(qwq)。。。

    整体二分大法吼啊!

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

  • 相关阅读:
    php_sphinx安装使用
    深入Web请求过程(笔记)
    php的单例模式
    解决rsync 同步auth failed on module问题
    生成shadow中hash字串
    python程序不支持中文
    xshell4无法使用小键盘问题解决
    LVS客户端启动脚本
    更改linux系统提示信息
    使用md5判断网站内容是否被篡改
  • 原文地址:https://www.cnblogs.com/wxq1229/p/12306780.html
Copyright © 2020-2023  润新知