复习一下整体二分

    前阵子颓废了一下,学了个整体二分和cdq分治,感觉还是比较神奇的一个东西,写篇博客总结一下。

    关于整体二分

        http://www.cnblogs.com/zig-zag/archive/2013/04/18/3027707.html先膜拜下Zig_zag神犇。。。
      
整体二分是一种非常神奇的离线算法。

      带修改的区间查询第k小,树套树、分块什么的其实可以轻松秒掉,但是代码量都非常巨大,而整体二分在允许离线的情况下,可以完美的在O(n log^2 n)的时间内解决这个问题,且代码量极短。
考虑对于单个询问,我们可以二分答案,通过判断比mid小的数有多少个来缩小范围。对于多个询问我们同样可以这样做,每次二分将操作序列分成两个序列,对于询问操作,如果比二分的mid小的数大于k-1个,那么就将它放到左序列,否则就将它放到右序列;对于修改操作,如果修改的数小于mid,即对于左序列的二分有影响,就将它放到左序列,否则就将它放到右序列。当 修改的数小于mid时,我们记录一下其对右序列询问的贡献,这样再对左序列和右序列分别进行二分就可以了。每次插入和询问操作,很容易就想到用树状数组来维护,于是就完美的解决了这个代码量巨大的问题。
      注意,二分前必须保证操作序列按照时间顺序排序。

/**************************************************************
    Problem: 1901
    User: Oxer
    Language: C++
    Result: Accepted
    Time:332 ms
    Memory:3176 kb
****************************************************************/
 
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<cerrno>
using namespace std;
 
struct yts
{
    int x,y,cur,id,op,k;
}q[30010],t[30010];
 
int tmp[30010],a[30010],ans[30010];
int c[30010];
bool mark[30010];
int n,m,cnt,num;
 
int lowbit(int x)
{
    return x&(-x);
}
 
void add(int i,int x)
{
    while (i<=n)
    {
        c[i]+=x;
        i+=lowbit(i);
    }
}
inline void assert(int x){
    if (x){
        int z=12;
        int x= z/(z-12);
    }
}
int query(int i)
{
    int tmp=0;
    while (i>0)
    {
        tmp+=c[i];
        i-=lowbit(i);
    }
    return tmp;
}
 
void solve(int l,int r,int L,int R)
{
    if (l>r) return;
    if (L==R)
    {
        for (int i=l;i<=r;i++)
          if (q[i].op==3) ans[q[i].id]=L;
        return;
    }
    int mid=(L+R)/2;
    for (int i=l;i<=r;i++)
    {
        if (q[i].op==1 && q[i].y<=mid) add(q[i].x,1);
        if (q[i].op==2 && q[i].y<=mid) add(q[i].x,-1);
        if (q[i].op==3) tmp[i]=query(q[i].y)-query(q[i].x-1);
    }
    for (int i=l;i<=r;i++)
    {
        if (q[i].op==1 && q[i].y<=mid) add(q[i].x,-1);
        if (q[i].op==2 && q[i].y<=mid) add(q[i].x,1);
    }
    int ct=0;
    for (int i=l;i<=r;i++)
    {
        if (q[i].op==3)
        {
            if (q[i].cur+tmp[i]>q[i].k-1) ct++,mark[i]=1;
            else q[i].cur+=tmp[i],mark[i]=0;
        }
        else
        {
            if (q[i].y<=mid) ct++,mark[i]=1;
            else mark[i]=0;
        }
    }
    int l1=l,l2=l+ct;
    for (int i=l;i<=r;i++) 
      if (mark[i]) t[l1++]=q[i];
      else t[l2++]=q[i];
    for (int i=l;i<=r;i++) q[i]=t[i];
    solve(l,l1-1,L,mid);
    solve(l1,l2-1,mid+1,R);
}
 
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        q[++num].x=i;q[num].y=a[i];q[num].op=1;q[num].id=0;
    }
    for (int i=1;i<=m;i++)
    {
        char opt[5];
        scanf("%s",opt);
        if (opt[0]=='C')
        {
            int x,y;
            scanf("%d%d",&x,&y);
            q[++num].op=2;q[num].x=x;q[num].y=a[x];q[num].id=0;
            q[++num].op=1;q[num].x=x;q[num].y=y;q[num].id=0;
            a[x]=y;
        }
        else
        {
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            q[++num].op=3;q[num].x=x;q[num].y=y;q[num].k=z;q[num].id=++cnt;
        }
    }
    solve(1,num,0,1000000000);
    for (int i=1;i<=cnt;i++) printf("%d\n",ans[i]);
    return 0;
}

bzoj3110  http://www.lydsy.com/JudgeOnline/showsource.php?id=940098
给树套树跪了(至今还不会树套树)。。。和例题差不多,加了些小操作,用区间修改区间查询搞一搞就好了,还现学了个树状数组区间修改区间查询,真心快呀。




跪一下写线段树的神犇。。。
/**************************************************************
    Problem: 3110
    User: Oxer
    Language: C++
    Result: Accepted
    Time:1116 ms
    Memory:7620 kb
****************************************************************/
 
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iostream>
 
using namespace std;
 
struct yts
{
    int x,y,op,cur,k,id;
}q[100010],t[100010];
 
int tmp[100010],ans[100010],c[2][100010];
bool mark[100010];
int n,m,num,cnt;
 
int lowbit(int x)
{
    return x&(-x);
}
 
void modify(int o,int i,int x)
{
    while (i<=n)
    {
        c[o][i]+=x;
        i+=lowbit(i);
    }
}
 
int query(int o,int i)
{
    int tmp=0;
    while (i>0)
    {
        tmp+=c[o][i];
        i-=lowbit(i);
    }
    return tmp;
}
 
void solve(int l,int r,int L,int R)
{
    if (l>r) return;
    if (L==R)
    {
        for (int i=l;i<=r;i++)
          if (q[i].op==2) ans[q[i].id]=L;
        return;
    }
    int mid=(L+R)/2;
    for (int i=l;i<=r;i++)
    {
        if (q[i].op==1)
        {
            if (q[i].k>mid)
            {
                modify(0,q[i].x,1);modify(1,q[i].x,q[i].x-1);
                modify(0,q[i].y+1,-1);modify(1,q[i].y+1,-q[i].y);
            }
        }
        else tmp[i]=(query(0,q[i].y)*q[i].y-query(1,q[i].y))-(query(0,q[i].x-1)*(q[i].x-1)-query(1,q[i].x-1));
    }
    for (int i=l;i<=r;i++)
      if (q[i].op==1 && q[i].k>mid)
      {
        modify(0,q[i].x,-1);modify(1,q[i].x,1-q[i].x);
        modify(0,q[i].y+1,1);modify(1,q[i].y+1,q[i].y);
      }
    int ct=0;
    for (int i=l;i<=r;i++)
    {
        if (q[i].op==2)
        {
            if (q[i].cur+tmp[i]>q[i].k-1) mark[i]=1;
            else ct++,q[i].cur+=tmp[i],mark[i]=0;
        }
        else
        {
            if (q[i].k>mid) mark[i]=1;
            else ct++,mark[i]=0;
        }
    }
    int l1=l,l2=l+ct;
    for (int i=l;i<=r;i++)
      if (!mark[i]) t[l1++]=q[i];
      else t[l2++]=q[i];
    for (int i=l;i<=r;i++) q[i]=t[i];
    solve(l,l1-1,L,mid);
    solve(l1,l2-1,mid+1,R);
}
 
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++)
    {
        scanf("%d%d%d%d",&q[i].op,&q[i].x,&q[i].y,&q[i].k);
        if (q[i].op==2) q[i].id=++cnt;
    }
    solve(1,m,-n,n);
    for (int i=1;i<=cnt;i++) printf("%d\n",ans[i]);
    return 0;
}


bzoj2527  http://www.lydsy.com/JudgeOnline/problem.php?id=2527
      这也是一道比较经典的整体二分题目,二分一下时刻,每次二分把它保证操作的时刻<=mid,然后查询一下就可以了。
小技巧:在操作序列最后加入一个极限操作,保证边界不会溢出。
hzwer学长的代码风格确实精简。。。
/**************************************************************
    Problem: 2527
    User: Oxer
    Language: C++
    Result: Accepted
    Time:12952 ms
    Memory:19324 kb
****************************************************************/
 
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<vector>
#define maxn 300010
#define ll long long
#define inf 1000000000
 
using namespace std;
 
ll c[maxn];
int l[maxn],r[maxn],val[maxn],p[maxn];
int tmp[maxn],id[maxn],ans[maxn];
bool mark[maxn];
int n,m,k,T;
vector<int> a[maxn];
 
int lowbit(int x)
{
    return x&(-x);
}
 
void add(int i,int x)
{
    while (i<=m)
    {
        c[i]+=x;
        i+=lowbit(i);
    }
}
 
ll query(int i)
{
    ll q=0;
    while (i>0)
    {
        q+=c[i];
        i-=lowbit(i);
    }
    return q;
}
 
void opera(int i,int f)
{
    if (l[i]<=r[i]) add(l[i],f*val[i]),add(r[i]+1,-f*val[i]);
    else add(1,f*val[i]),add(l[i],f*val[i]),add(r[i]+1,-f*val[i]);
}
 
void solve(int l,int r,int L,int R)
{
    if (l>r) return;
    if (L==R)
    {
        for (int i=l;i<=r;i++)
          ans[id[i]]=L;
        return;
    }
    int mid=(L+R)/2;
    while (T<=mid) {T++;opera(T,1);}
    while (T>mid) {opera(T,-1);T--;}
    ll tot=0;int now,cnt=0;
    for (int i=l;i<=r;i++)
    {
        tot=0;now=id[i];
        for (int j=0;j<a[now].size();j++)
        {
            tot+=query(a[now][j]);
            if (tot>=p[now]) break;
        }
        if (tot>=p[now]) mark[i]=1,cnt++;
        else mark[i]=0;
    }
    int l1=l,l2=l+cnt;
    for (int i=l;i<=r;i++)
      if (mark[i]) tmp[l1++]=id[i];
      else tmp[l2++]=id[i];
    for (int i=l;i<=r;i++) id[i]=tmp[i];
    solve(l,l1-1,L,mid);
    solve(l1,l2-1,mid+1,R);
}
 
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++)
    {
        int x;
        scanf("%d",&x);
        a[x].push_back(i);
    }
    for (int i=1;i<=n;i++) scanf("%d",&p[i]);
    scanf("%d",&k);
    for (int i=1;i<=k;i++)
      scanf("%d%d%d",&l[i],&r[i],&val[i]);
    for (int i=1;i<=n;i++) id[i]=i;
    k++;
    l[k]=1;r[k]=m;val[k]=inf;
    solve(1,n,1,k);
    for (int i=1;i<=n;i++)
      if (ans[i]!=k) printf("%d\n",ans[i]);
      else printf("NIE\n");
    return 0;
}

bzoj2738  http://www.lydsy.com/JudgeOnline/problem.php?id=2738
      第一眼看到数据范围吓傻了。。。什么?!二维可持久化线段树?!原谅我太弱了。。。仔细一看还是比较水的,插入的时候按照权值排序,二分一下答案,每次保证插入的权值<=mid,然后写个二维树状数组查询就好了。应该算是比较经典的例题了。
/**************************************************************
    Problem: 2738
    User: Oxer
    Language: C++
    Result: Accepted
    Time:11524 ms
    Memory:7276 kb
****************************************************************/
 
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iostream>
 
using namespace std;
 
struct yts
{
    int x,y,val;
}a[510*510];
 
struct yts1
{
    int x1,y1,x2,y2,k;
}q[60010];
 
int c[510][510];
int tmp[60010],id[60010],ans[60010];
bool mark[60010];
int n,m,T,cnt;
 
bool cmp(yts x,yts y)
{
    return x.val<y.val;
}
 
int lowbit(int x)
{
    return x&(-x);
}
 
void add(int k,int x)
{
    for (int i=a[k].x;i<=n;i+=lowbit(i))
      for (int j=a[k].y;j<=n;j+=lowbit(j))
        c[i][j]+=x;
}
 
int get(int x,int y)
{
    int tmp=0;
    for (int i=x;i>0;i-=lowbit(i))
      for (int j=y;j>0;j-=lowbit(j))
        tmp+=c[i][j];
    return tmp;
}
 
int query(int i)
{
    int x1=q[i].x1,y1=q[i].y1,x2=q[i].x2,y2=q[i].y2;
    return get(x2,y2)-get(x2,y1-1)-get(x1-1,y2)+get(x1-1,y1-1);
}
 
void solve(int l,int r,int L,int R)
{
    if (l>r) return;
    if (L==R) return;
    int mid=(L+R)/2;
    while (T<cnt && a[T+1].val<=mid) T++,add(T,1);
    while (a[T].val>mid) add(T,-1),T--;
    int cnt=0;
    for (int i=l;i<=r;i++)
    {
        int x=query(id[i]);
        if (x>q[id[i]].k-1) mark[i]=1,ans[id[i]]=mid,cnt++;
        else mark[i]=0;
    }
    int l1=l,l2=l+cnt;
    for (int i=l;i<=r;i++)
      if (mark[i]) tmp[l1++]=id[i];
      else tmp[l2++]=id[i];
    for (int i=l;i<=r;i++) id[i]=tmp[i];
    solve(l,l1-1,L,mid);
    solve(l1,l2-1,mid+1,R);
}
 
int main()
{
    scanf("%d%d",&n,&m);
    int mx=0;
    for (int i=1;i<=n;i++)
      for (int j=1;j<=n;j++)
      {
        a[++cnt].x=i;a[cnt].y=j;
        scanf("%d",&a[cnt].val);
        mx=max(a[cnt].val,mx);
      }
    sort(a+1,a+cnt+1,cmp);
    for (int i=1;i<=m;i++) scanf("%d%d%d%d%d",&q[i].x1,&q[i].y1,&q[i].x2,&q[i].y2,&q[i].k);
    for (int i=1;i<=m;i++) id[i]=i;
    solve(1,m,0,mx+1);
    for (int i=1;i<=m;i++) printf("%d\n",ans[i]);
    return 0;
}

总结:对于询问和修改混合的题目,要记录一下前面的修改操作对后面的贡献。对比一下,整体二分其实是优于许多高端的数据结构的,但是出难题也是比较难想的。

附:树状数组的区间修改、区间查询
记i-n的共同增量为delta[i],前i项的和为sum[i],则query(l,r)=sum[r]-sum[l-1],现在的关键是怎样求sum[i]。
sum[i]=sigma(a[1……i])+i*delta[1]+(i-1)*delta[2]+……+1*delta[i]
 =sigma(a[1……i])+sigma(delta[j]*(i+1-j))
 =sigma(a[1……i])+(i+1)*sigma(delta[j])-sigma(j*delta[j])
这样只要维护一下sigma(delta[i])和sigma(i*delta[i]),问题就解决了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值