zoukankan      html  css  js  c++  java
  • CDQ分治与整体二分小结

    前言

      这是一波强行总结。

      下面是一波瞎比比。

      这几天做了几道CDQ/整体二分,感觉自己做题速度好慢啊。

      很多很显然的东西都看不出来 分治分不出来 打不出来 调不对

      上午下午晚上的效率完全不一样啊。

      完蛋.jpg 绝望.jpg。

    关于CDQ分治

      CDQ分治,求的是三维偏序问题都知道的。

      求法呢,就是在分治外面先把一维变成有序

      然后分治下去,左边(l,mid)关于右边(mid+1,r)就不存在某一维的逆序了,所以只有两维偏序了。

      这个时候来一波"树状数组求逆序对"的操作搞一下二维偏序

      就可以把跨过中线的,左边更新右边的情况计算出来。

      注意:只计算左边的操作对右边的询问的贡献!

      然后左右两边递归处理就好了。

      正确性:按照线段树的形态递归的CDQ分治,保证每一对三元组在第一维划分的线段树上都有且仅有一个LCA(这不废话吗),而这一组答案就会且仅会在LCA处计算。如果在LCA下面,点对不在一个work内自然不会计算。如果在LCA上面了,点对就在同一侧,不会互相更新。

      复杂度:设一次work的复杂度是f(len),则复杂度是O(f(n)logn)。

      一般都在分治里用树状数组,一般的复杂度就是O(nlog2n)的。

      一般是这样的套路:假设三维偏序分别为a,b,c;

      在main函数里保证a递增。

      然后在CDQ里先分治左右,传下去的时候a仍然递增,不破坏性质。

      然后分治完左右两边后,需保证左右两边分别b都是递增的(a不重要)。

      然后就是类似归并排序的操作了。

      此时左边的a肯定都小于右边的a,那么如果对于一个右边的元素

      之前类似归并的操作就可以保证所有小于b的左边的元素都已经遍历过。

      那么找c也小于它的?值域线段树/树状数组等数据结构维护一下就好了。

      然后你这么归并了一波后,就发现统计完答案后b是有序递增的了(这个时候a已经不重要了)。

      对于上层操作,符合"左右两边分别b是递增的"了。

      BZOJ陌上花开竟然是权限题?这是在搞笑。

      好吧BZOJ动态逆序对,之前写过的,做两次CDQ就好了。

      BZOJ稻草人,也是CDQ,加个单调栈。

    还有一个就是高维偏序问题。

    cogs上的2479 HZOI2016 偏序 就是四维偏序板子。

    后面还有两个加强版,到了七维,不是CDQ干的事情,详情请见这个PPT

    校内交流所以做的不是很严谨(吐舌)

    这里只谈论四维偏序,即a<a'   b<b'   c<c'   d<d'。

    做法是喜闻乐见的CDQ套CDQ套树状数组。

    有个很妙的博客:Candy?

    首先在外面按照a排好序。

    进第一层CDQ。先递归处理,然后标记本来是在mid左边还是右边的,左1右0,然后按b排序。

    还是只统计左边部分跨过中线对右边部分的贡献。

    按照b排好序后,就变成了统计标记为0的点的"在它左边的、标记为1的、(c,d)都小于它的点的个数"。

    "在它左边+(c,d)都小于它" = 三维偏序。

    复制到另一个数组里再做一次cdq就可以了。

    复杂度O(nlog^3n)。

    #include    <iostream>
    #include    <cstdio>
    #include    <cstdlib>
    #include    <algorithm>
    #include    <vector>
    #include    <cstring>
    #include    <queue>
    #include    <complex>
    #include    <stack>
    #define LL long long int
    #define dob double
    #define FILE "partial_order"
    //#define FILE "CDQ"
    using namespace std;
    
    const int N = 100010;
    struct Data{int a,b,c,id;}p[N],que[N],que2[N];
    int n,vis[N],tim,T[N];
    LL Ans;
    
    inline int gi(){
      int x=0,res=1;char ch=getchar();
      while(ch>'9'||ch<'0'){if(ch=='-')res*=-1;ch=getchar();}
      while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
      return x*res;
    }
    
    inline void update(int x){
      for(;x<=n;x+=x&-x){
        if(vis[x]!=tim)T[x]=0,vis[x]=tim;
        T[x]++;
      }
    }
    
    inline int query(int x,int ans=0){
      for(;x;x-=x&-x){
        if(vis[x]!=tim)T[x]=0,vis[x]=tim;
        ans+=T[x];
      }
      return ans;
    }
    
    inline void cdq(int l,int r){
      if(l==r)return;
      int mid=(l+r)>>1,i=l,j=mid+1,k=l;
      cdq(l,mid);cdq(mid+1,r);tim++;
      while(i<=mid && j<=r){
        if(que[i].b<que[j].b){
          if(que[i].id)update(que[i].c);
          que2[k++]=que[i++];
        }
        else{
          if(!que[j].id)Ans+=query(que[j].c);
          que2[k++]=que[j++];
        }
      }
      while(i<=mid)que2[k++]=que[i++];
      while(j<=r){
        if(!que[j].id)Ans+=query(que[j].c);
        que2[k++]=que[j++];
      }
      for(k=l;k<=r;++k)que[k]=que2[k];
    }
    
    inline void CDQ(int l,int r){
      if(l==r)return;
      int mid=(l+r)>>1,i=l,j=mid+1,k=l;
      CDQ(l,mid);CDQ(mid+1,r);
      while(i<=mid && j<=r){
        if(p[i].a<p[j].a)que[k]=p[i++],que[k++].id=1;
        else que[k]=p[j++],que[k++].id=0;
      }
      while(i<=mid)que[k]=p[i++],que[k++].id=1;
      while(j<=r)que[k]=p[j++],que[k++].id=0;
      for(k=l;k<=r;++k)p[k]=que[k];cdq(l,r);
    }
    
    int main()
    {
      freopen(FILE".in","r",stdin);
      freopen(FILE".out","w",stdout);
      n=gi();
      for(int i=1;i<=n;++i)p[i].a=gi();
      for(int i=1;i<=n;++i)p[i].b=gi();
      for(int i=1;i<=n;++i)p[i].c=gi();
      CDQ(1,n);printf("%lld
    ",Ans);
      fclose(stdin);fclose(stdout);
      return 0;
    }
    CDQ套CDQ

     

    关于整体二分

      整体二分主要是把所有询问放在一起二分答案,然后把操作也一起分治。

      什么时候用呢?

      当你发现多组询问可以离线的时候

      当你发现询问可以二分答案而且check复杂度对于单组询问可以接受的时候

      当你发现询问的操作都是一样的的时候

      你就可以使用整体二分这个东西了。

      具体做法讲起来有些玄学,其实类似主席树转化到区间的操作或者线段树上二分。

      想想:二分答案的时候,对于一个答案,是不是有些操作是没用的,有些操作贡献是不变的?

      比如二分一个时间,那么时间后面发生的操作就是没有用的,时间前面的贡献是不变的。

      二分一个最大值,比mid大的都是没用的,比mid小的个数是一定的。

      整体二分就是利用了这么一个性质。

      平时我们二分答案,都是这么写的:

    inline int check(int mid){
      int num=0;
      for(int i=1;i<=m;++i)
        if(calc(i,mid))
          num++;
      return num;
    }
    
    ...
    
    int l=...,r=...,ans=-1;
    while(l<=r){
      int mid=(l+r)>>1;
      if(check(mid)<k)l=mid+1;
      else ans=mid,r=mid-1;
    }
    1.0

      这种写法已经很优秀了。但是如果有q次询问,复杂度就是O(qmlogn)。

      换种方式:

    inline bool check(int mid){
      int t1=0,t2=0;
      for(int i=1;i<=m;++i){
        if(calc(i,mid))que[1][++t1]=i;
        else que[2][++t2]=i;
      }
      if(t1>=k){
        m=t1;
        for(int i=1;i<=m;++i)opt[i]=que[1][i];
        return 1;
      }
      else{
        m=t2;
        for(int i=1;i<=m;++i)opt[i]=que[2][i];
        k-=t1;return 0;
      }
    }
    
    ...
    
    int l=...,r=...,ans=-1;
    while(l<=r){
      int mid=(l+r)>>1;
      if(check(mid))r=mid-1,ans=mid;
      else l=mid+1;
    }
    2.0

      (如上面代码有错误请指出)

      分析起来复杂度并没有什么改变......

      但是如果把二分答案看成一棵二叉树,每个点(区间[l,r])的权值为check的操作数。

      把当前是第几次二分看成这个区间的深度(层)。

      每一层的区间相互没有交。

      那么有一个优秀的性质:只有log层,每一层的点权和为O(m)。

      所以这个时候对于多组询问一起处理,复杂度为O((m+q)logn)。

      

      二分答案,然后把没有用的操作扫进右边,和答案在[mid+1,r]的询问一起递归处理。

      把有用的操作放进左边,减去不变的贡献,和答案在[l,mid]的一起递归处理。

      注意答案在[mid+1,r]的询问要算上放进了左边的操作的贡献,开个变量记下来/直接减掉都可以。

      注意整体二分在solve内的复杂度一定只能与区间长度线性相关,不能每次都有别的复杂度!

      比如一次solve的复杂度是O(lenlogn)就可以,O(len+sqrt(n))就不行。

      大概就是这么一个东西。

      复杂度?和CDQ是一样的,都是O(f(len)logn)。

      例题?BZOJ3110 K大数查询 Codevs Meteors。

      一样的套路了。

    关于一些要注意的地方

      归并一定要把剩下的搞完!每次我都忘记这码子事!

      树状数组不能暴力清零!记个time或者依葫芦画瓢减回去都可以,一定不能清零!

      不要在CDQ里面套sort,太慢辣!(一定进不了第一版的!)

     

  • 相关阅读:
    MySQL数据库优化详解(收藏)
    怎么设置Linux swap分区?方法教程
    js获取IP地址多种方法实例教程
    JQuery设置获取下拉菜单选项的值 多实例
    JQuery中serialize()、serializeArray()和param()用法举例
    javascript 获取函数形参个数
    mysql SQLyog导入csv数据失败怎么办?
    今天离职了!
    Asp.Net Core 使用Quartz基于界面画接口管理做定时任务
    Asp.Net Core中使用Swagger,你不得不踩的坑
  • 原文地址:https://www.cnblogs.com/fenghaoran/p/7436593.html
Copyright © 2011-2022 走看看