码迷,mamicode.com
首页 > 其他好文 > 详细

整体二分

时间:2020-02-14 13:15:17      阅读:65      评论:0      收藏:0      [点我收藏+]

标签:math   ble   ranking   前缀   inf   nlog   names   条件   com   

还是把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;
}

和树状数组套主席树的对比:

整体二分:

技术图片

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

树状数组套主席树:

技术图片

\(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)\)吧。。。

整体二分

标签:math   ble   ranking   前缀   inf   nlog   names   条件   com   

原文地址:https://www.cnblogs.com/wxq1229/p/12306780.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!