zoukankan      html  css  js  c++  java
  • CDQ分治与整体二分学习笔记

     CDQ分治部分

         CDQ分治是用分治的方法解决一系列类似偏序问题的分治方法,一般可以用KD-tree、树套树或权值线段树代替。

         三维偏序,是一种类似LIS的东西,但是LIS的关键字只有两个,数组下标和权值,三维偏序问题的权值有两个,且必须A[I]<A[J]且B[I]<B[j]。

         把这个问题放到平面上,就是一个点在另一个点的左下方。

         那么如何求?

         CDQ分治的主要过程是二分整个区间,把左区间看成产生贡献的区间,于是我们在左区间进行操作,在右区间统计答案,用归并排序的方法求解。

         对于这道题,我们二分整个区间,对A做归并排序,归并时对B维护一个树状数组,在左区间更改,右区间查询就可以了。

         例题1

         陌上花开(bzoj3262)裸的三维偏序

         我们按照a为第一关键字,b为第二关键字,c为第三关键字排序,其实相当于把a去掉了,换成了数组下标,因为本质相同的元素答案是相同的,所以手动去重后直接CDQ分治求解。

        Code

        

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #define low (x&(-x))
    #define N 200009
    #define M 100009
    using namespace std;
    typedef long long ll;
    int k,n,ans2[M],be[M];
    ll f[M],tr[N],ans[M];
    struct zzh
    {
        int a,b,c,id,size;
        bool operator < (const zzh &u)const
        {
            if(b!=u.b)return b<u.b;
            if(c!=u.c)return c<u.c;
            return id<u.id;
        } 
    }ji[M],a[M],t[M];
    bool cmp(zzh a,zzh b)
    {
            if(a.a!=b.a)return a.a<b.a;
            if(a.b!=b.b)return a.b<b.b;
            if(a.c!=b.c)return a.c<b.c;
            return a.id<b.id;
    }
    void mem(int x){while(x<=k)tr[x]=0,x+=low;}
    void gai(int x,int y){while(x<=k)tr[x]+=y,x+=low;}
    int query(int x)
    {
        int ans=0;
        while(x){ans+=tr[x];x-=low;}
        return ans;
    }
    void cdq(int l,int r)
    {
        if(l==r)return;
        int mid=(l+r)>>1;
        cdq(l,mid);cdq(mid+1,r);
        int p=l,q=mid+1,o=l-1;
        while(p<=mid&&q<=r)
        {
            if(t[p]<t[q])
              gai(t[p].c,t[p].size),ji[++o]=t[p++];
            else ans[t[q].id]+=query(t[q].c),ji[++o]=t[q++];
        }
        while(p<=mid)ji[++o]=t[p++];
        while(q<=r)ans[t[q].id]+=query(t[q].c),ji[++o]=t[q++];
        for(int i=l;i<=r;++i)
          mem(ji[i].c),t[i]=ji[i];
    }
    bool pd(int x,int now){
        if(!now)return 0;
        return ((a[x].a==a[now].a)&&(a[x].b==a[now].b)&&(a[x].c==a[now].c));
    }
    int main()
    {
    //    freopen("233.out","w",stdout);
        scanf("%d%d",&n,&k);
        for(int i=1;i<=n;++i)
          scanf("%d%d%d",&a[i].a,&a[i].b,&a[i].c),a[i].id=i,a[i].size=1;
        sort(a+1,a+n+1,cmp);int now=0;
        for(int i=1;i<=n;++i)
        {
            if(!pd(i,i-1))
              t[++now]=a[i],t[now].id=now;
            else t[now].size++;
            be[i]=now;
        }
        cdq(1,now);
        for(int i=1;i<=now;++i)ans[t[i].id]+=t[i].size-1;
        for(int i=1;i<=n;++i)f[i]=ans[be[i]];
        for(int i=1;i<=n;++i)ans2[f[i]]++;
        for(int i=0;i<n;++i)printf("%d
    ",ans2[i]);
        return 0;
    }

         bzoj4553 [Tjoi2016&Heoi2016]序列

         也是一个类LIS问题,只不过条件有些特殊。

         观察题目可知,i和j都在LIS中需满足a[i]<=a[j]&&a[i]<=l[j]&&r[i]<=a[j],其中l[i]为a[i]的最小值,r[i]为其最大值,第一个条件可省略,再加上下标,正好是三个。

         我们先CDQ左区间,再把左区间按照a排序,右区间按照l排序,归并时将左区间的r加入树状数组,右区间用a查询,做完后在CDQ右区间就行了。

          f数组记得取max。

    Code

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define N 100009
    #define shi 100000
    #define lowbit(x) (x&(-x))
    using namespace std;
    int tr[N],n,m,f[N]; 
    inline int rd()
    {
        int x=0;
        char c=getchar();
        while(!isdigit(c))c=getchar();
        while(isdigit(c))
        {
            x=(x<<1)+(x<<3)+(c^48);
            c=getchar();
        }
        return x;
    }
    struct zzh
    {
        int l,r,a,id;
    }ji[N];
    void add(int x,int y){while(x<=shi){tr[x]=max(tr[x],y);x+=lowbit(x);}}
    void clear(int x){while(x<=shi){tr[x]=0;x+=lowbit(x);}}
    int query(int x){
      int ans=0;
      while(x){
      ans=max(tr[x],ans);
      x-=lowbit(x);
      }
      return ans;
    }
    bool cmp1(zzh a,zzh b){return a.a<b.a;}
    bool cmp2(zzh a,zzh b){return a.l<b.l;}
    bool cmp3(zzh a,zzh b){return a.id<b.id;}
    void cdq(int l,int r)
    {
        if(l==r)return;
        int mid=(l+r)>>1;
        cdq(l,mid);
        sort(ji+l,ji+mid+1,cmp1);sort(ji+mid+1,ji+r+1,cmp2);
        int p=l,q=mid+1,o=l-1;
        while(p<=mid&&q<=r)
        {
            if(ji[p].a<=ji[q].l)add(ji[p].r,f[ji[p].id]),p++; 
            else f[ji[q].id]=max(query(ji[q].a)+1,f[ji[q].id]),q++;
        }
        while(q<=r)f[ji[q].id]=max(query(ji[q].a)+1,f[ji[q].id]),q++;
        for(int i=l;i<=mid;++i)clear(ji[i].r);
        sort(ji+l,ji+r+1,cmp3);
        cdq(mid+1,r);
    }
    int main()
    {
        n=rd();m=rd();
        for(int i=1;i<=n;++i)
          ji[i].l=ji[i].r=ji[i].a=rd(),ji[i].id=i;
          int x,y;
        for(int j=1;j<=m;++j)
          {
              x=rd();y=rd();
              ji[x].l=min(ji[x].l,y);
              ji[x].r=max(ji[x].r,y);
          }
        for(int i=1;i<=n;++i)f[i]=1;    
        cdq(1,n);int ans=0;
        for(int i=1;i<=n;++i)ans=max(ans,f[i]);
        cout<<ans;
        return 0;
    } 

     整体二分部分

          整体二分本质和CDQ差不多,都是基于分治的算法,实现起来比较简单(只要能够深入理解)。

         例题(就写过一道)

         [ZJOI2013]K大数查询

         有N个位置,M个操作。操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少。

         这题什么神奇分块树套树的都能过,整体二分也是一个很经典的解法。

         我们现在有一个操作和询问混合的序列,我们需要通过一些操作把它分成两个序列,在这两个序列在时间维度上是单调递增的(在原序列中的相对位置不变),但答案的值域分别小了一半,虽然序列并没有严格二分,但答案值域减小了一半,保证了我们分治的复杂度。

         考虑我们有一个序列,里面有在l-r内每个位置插入一个c,还有询问一个区间的第K大,目前的值域为-inf---inf.

         我们按照时间的顺序把这个序列扫一遍,遇到操作大于等于mid就把这个l---r+1,当我们遇到一个询问时,我们就查l---r有多少数,实际就是查l---r有多少数是大于等于mid,如果已经够K个了,说明我的第K大一定是>=mid的(显然),所以我们把它放到右序列,否则,它就该在左边,同时我们要把K减去我们查询的结果,意思就是说我们已经数了这么多个了。

          同理,对于每个操作,如果>=mid就放到右序列,否则就放到左序列。

          然后我们完成了一项使命,把答案值域缩小了一半,接下来继续分治就好了。

          边界l==r,此时我的所有询问的答案都是这个值。

          时间复杂度,分治nlogn,加上线段树维护操作一个log,总复杂度nlog2n。

         Code

    #include<iostream>
    #include<cstdio>
    #define N 50009
    using namespace std;
    long long tr[N<<2];
    int la[N<<2],n,m,ans[N];
    struct ef
    {
        int l,r,tag,id;
        long long v;
    }a[N],q1[N],q2[N];
    inline void pushdown(int cnt,int l1,int l2)
    {
      tr[cnt<<1]+=la[cnt]*l1;tr[cnt<<1|1]+=la[cnt]*l2;
      la[cnt<<1]+=la[cnt];la[cnt<<1|1]+=la[cnt];la[cnt]=0;
    }
    long long query(int cnt,int l,int r,int L,int R)
    {
        if(l>=L&&r<=R)return tr[cnt];
        int mid=(l+r)>>1;
        if(la[cnt])pushdown(cnt,mid-l+1,r-mid);
        long long ans=0;
        if(mid>=L)ans+=query(cnt<<1,l,mid,L,R);
        if(mid<R)ans+=query(cnt<<1|1,mid+1,r,L,R);
        return ans;
    }
    void add(int cnt,int l,int r,int L,int R,int tag)
    {
        if(l>=L&&r<=R)
        {
           tr[cnt]+=(r-l+1)*tag;
           la[cnt]+=tag;
           return;
        }
        int mid=(l+r)>>1;
        if(la[cnt])pushdown(cnt,mid-l+1,r-mid); 
        if(mid>=L)add(cnt<<1,l,mid,L,R,tag);
        if(mid<R)add(cnt<<1|1,mid+1,r,L,R,tag);
        tr[cnt]=tr[cnt<<1]+tr[cnt<<1|1];
    }
    void solve(int st,int en,int l,int r)
    {
        if(l==r)
        {
            for(int i=st;i<=en;++i)
              if(a[i].tag==2)ans[a[i].id]=l;
            return;
        }
        int mid=(l+r)>>1,ll=0,rr=0;
        for(int i=st;i<=en;++i)
            if(a[i].tag==1)
            {
                if(a[i].v<=mid)q1[++ll]=a[i];
                else
                {
                    add(1,1,n,a[i].l,a[i].r,1);
                    q2[++rr]=a[i];
                }
            }
            else
            {
                long long val=query(1,1,n,a[i].l,a[i].r);
                if(val>=a[i].v)
                {
                    q2[++rr]=a[i];
                }
                else a[i].v-=val,q1[++ll]=a[i];
            }
        for(int i=st;i<=en;++i)
         if(a[i].tag==1&&a[i].v>mid)add(1,1,n,a[i].l,a[i].r,-1);
        for(int i=1;i<=ll;++i)a[st+i-1]=q1[i];
        for(int i=1;i<=rr;++i)a[st+ll+i-1]=q2[i];
        solve(st,st+ll-1,l,mid);solve(st+ll,en,mid+1,r);
    }
    int main()
    {
        scanf("%d%d",&n,&m);int tot=0;
        for(int i=1;i<=m;++i)
        {
        scanf("%d%d%d%lld",&a[i].tag,&a[i].l,&a[i].r,&a[i].v);
        if(a[i].tag==2)a[i].id=++tot;
        }
        solve(1,m,-n,n);
        for(int i=1;i<=tot;++i)printf("%d
    ",ans[i]);
        return 0;
    } 

     Dynamic Rankings

    给定一个含有n个数的序列a[1],a[2],a[3]……a[n],程序必须回答这样的询问:对于给定的i,j,k,在a[i],a[i+1],a[i+2]……a[j]中第k小的数是多少(1≤k≤j-i+1),并且,你可以改变一些a[i]的值,改变后,程序还能针对改变后的a继续回答上面的问题。你需要编一个这样的程序,从输入文件中读入序列a,然后读入一系列的指令,包括询问指令和修改指令。

    对于每一个询问指令,你必须输出正确的回答。

    Solution

    区间待修第k大,经典整体二分题。

    Code

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #define N 100002
    using namespace std;
    int tr[N<<1],ji[N<<1],top,tot,x,y,ans[N],n,m,aa,num[N];
    char c;
    inline void add(int x,int y){while(x<=top)tr[x]+=y,x+=x&-x;}
    inline int query(int x){int ans=0;while(x)ans+=tr[x],x-=x&-x;return ans;}
    struct sd{
        int x,y,tag,l,r,k;
    }q[N*3],a[N*3],b[N*3];
    void solve(int l,int r,int L,int R){
        if(L==R)
        {
          for(int i=l;i<=r;++i)
            if(q[i].l)ans[q[i].tag]=L;
          return;
        }
        int mid=(L+R)>>1,l1=0,l2=0;
        for(int i=l;i<=r;++i){
            if(q[i].x!=0){
                if(q[i].y<=mid)add(q[i].x,q[i].tag),a[++l1]=q[i];
                else b[++l2]=q[i];
            }
            else{
                int tmp=query(q[i].r)-query(q[i].l-1);
                if(tmp>=q[i].k)a[++l1]=q[i];
                else q[i].k-=tmp,b[++l2]=q[i];
            }
        } 
        for(int i=1;i<=l1;++i)if(a[i].x)add(a[i].x,-a[i].tag);
        for(int i=l;i<=l+l1-1;++i)q[i]=a[i-l+1];
        for(int i=l+l1;i<=r;++i)q[i]=b[i-l-l1+1];
        solve(l,l+l1-1,L,mid);solve(l+l1,r,mid+1,R);
    }
    char ge(){
        char c=getchar();
        while(c!='Q'&&c!='C')c=getchar();
        return c;
    }
    int main(){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;++i){
         scanf("%d",&x);ji[++top]=x;
         q[++tot].x=i;q[tot].y=x;q[tot].tag=1;
         num[i]=x;
       }
        for(int i=1;i<=m;++i){
         c=ge();
         if(c=='Q'){
             ++tot;
             scanf("%d%d%d",&q[tot].l,&q[tot].r,&q[tot].k);
             q[tot].tag=++aa;
         }
         else{
             scanf("%d%d",&x,&y);
             q[++tot].x=x;q[tot].y=num[x];q[tot].tag=-1;
             q[++tot].x=x;q[tot].y=y;q[tot].tag=1;
             num[x]=y;//care!!!!!
             ji[++top]=y;
         }
       }
         sort(ji+1,ji+top+1);
         top=unique(ji+1,ji+top+1)-ji-1;
         for(int i=1;i<=tot;++i)
           if(q[i].y)q[i].y=lower_bound(ji+1,ji+top+1,q[i].y)-ji;
         solve(1,tot,1,top);
         for(int i=1;i<=aa;++i)printf("%d
    ",ji[ans[i]]);
         return 0;
    } 
  • 相关阅读:
    TortoiseGit
    申请成功
    web.xml文件中配置ShallowEtagHeaderFilter需注意的问题
    消息队列调研
    二阶段提交
    ACID CAP BASE介绍
    SQL NULL Values
    HTTPS原理
    ID生成器详解
    如何变得更聪明
  • 原文地址:https://www.cnblogs.com/ZH-comld/p/9407852.html
Copyright © 2011-2022 走看看