zoukankan      html  css  js  c++  java
  • CDQ分治(学习笔记)

    离线算法——CDQ分治

      CDQ (SHY)显然是一个人的名字,陈丹琪(MM)(NOI2008金牌选手)。

    •      从归并开始(这里并没有从逆序对开始,是想直接引入分治思想,而不是引入处理对象)

      一个很简单的归并排序:一个乱序的数列,每次将其折半,类似于线段树这样的数据结构,每个子区间先处理好,最后汇总到上一层。

    其中层数不超过log(n)层,每次处理的复杂度是O(n)的,因此其复杂度为O(nlogn)。

      code:

    void merge_sort(int l,int r)
    {
        if(l==r)return;
        int mid=(l+r>>1);
        merge_sort(l,mid);merge_sort(mid+1,r);
        int i=l,j=mid+1,k=l;
        while(i<=mid&&j<=r)
        {
            if(a[i]<a[j])t[k++]=a[i++];
            else t[k++]=a[j++];
        }
        while(i<=mid)t[k++]=a[i++];
        while(j<=r)t[k++]=a[j++];
        go(i,l,r)a[i]=t[i];
    }
    •   简单应用(逆序对)

      逆序对就是求数列中满足i<j&&a[i]>a[j]的二元组(i,j)的对数。

      举个栗子(真好吃):1,4,3,8,4,3,8(val)

                1,2,3,4,5,6,7(pos)

      对于pos:(2,3),(2,6),(4,5),(4,6),(5,6)都是逆序对

      于是就我们可以由归并的性质:因为pos已经天然有序,因此我们只要看val就可以了。

      在merge时,如果右边的当前位置j比左边的位置i小,那么它一定比[i,mid]中的所有数都小,因此ans+=mid-i+1。

    •   从逆序对到二维偏序问题

      二维偏序问题其实就是把逆序对的pos打乱,且会重复,它可以理解为在平面直角坐标系中,有n个点(i,j),求每个点和(0,0)形成的矩形内有多少个点。

      双关键字排序后直接树状数组即可:

      例题:二维偏序

    #include<iostream>
    #include<algorithm>
    #include<cstdio>
    #include<cstring>
    #define lowbit(x) (x&-x)
    using namespace std;
    const int N=100010;
    struct star
    {
        int x,y;    
    }s[N];
    long long tarr[N];
    int n;
    long long ans;
    
    bool cmp(star a,star b)
    {
        return a.x==b.x?a.y<b.y:a.x<b.x;
    }
    
    void add(int pos,int val)
    {
        while(pos<=N)
        {
            tarr[pos]+=val;
            pos+=lowbit(pos);
        }
    }
    
    int query(int pos)
    {
        int res=0;
        while(pos)
        {
            res+=tarr[pos];
            pos-=lowbit(pos);
        }return res;
    }
    
    int main()
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%d%d",&s[i].x,&s[i].y);
        }
        sort(s+1,s+1+n,cmp);
        for(int i=1;i<=n;i++)
        {
            ans+=query(s[i].y);
            add(s[i].y,1);
        }
        cout<<ans;
    }
    •    从二维偏序到三维偏序

      板子:陌上花开

      有了之前的基础,三维偏序也很简单:

      我们按三关键字排序,(优先级a>b>c),对第二位进行归并排序,在merge时,对于左边的i和右边的j,如果第二维满足(bi<bj),则在树状数组中加入ci,直到存在某个bi不满足此关系,则j可以查询树状数组中所有ci<cj的三元组,由于之前对第一维的排序和对第二维的归并,前两维一定是满足条件的。这里有个去重还是挺烦的。

    code:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #define lowbit(x) (x&-x)
    using namespace std;
    const int N=100010;
    const int M=200010;
    struct node
    {
        int a,b,c,f,w;
    }e[N],t[N];
    int cnt;
    int n,m;
    int ans[N];
    int tarr[M];
    
    bool cmp(node x,node y)
    {
        return x.a==y.a?(x.b==y.b?x.c<y.c:x.b<y.b):x.a<y.a;
    }
    
    void add(int pos,int val)
    {
        while(pos<=m)
        {
            tarr[pos]+=val;
            pos+=lowbit(pos);
        }
    }
    
    int query(int pos)
    {
        int res=0;
        while(pos)
        {
            res+=tarr[pos];
            pos-=lowbit(pos);
        }return res;
    }
    
    void CDQ(int l,int r)
    {
        if(l==r)return;
        int mid=(l+r>>1);
        CDQ(l,mid);CDQ(mid+1,r);
        int i=l,j=mid+1,k=l;
        while(i<=mid&&j<=r)
        {
            if(e[i].b<=e[j].b)add(e[i].c,e[i].w),t[k++]=e[i++];
            else e[j].f+=query(e[j].c),t[k++]=e[j++];
        }
        while(i<=mid)add(e[i].c,e[i].w),t[k++]=e[i++];
        while(j<=r)e[j].f+=query(e[j].c),t[k++]=e[j++];
        for(int i=l;i<=mid;i++)add(e[i].c,-e[i].w);
        for(int i=l;i<=r;i++)e[i]=t[i];
    }
    
    int main()
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
        scanf("%d%d%d",&e[i].a,&e[i].b,&e[i].c),e[i].w=1;
        sort(e+1,e+1+n,cmp);
        cnt=1;
        for(int i=2;i<=n;i++)
        {
            if(e[i].a==e[cnt].a&&e[i].b==e[cnt].b&&e[i].c==e[cnt].c)
            e[cnt].w++;
            else e[++cnt]=e[i];
        }
        CDQ(1,cnt);
        for(int i=1;i<=cnt;i++)ans[e[i].f+e[i].w-1]+=e[i].w;
        for(int i=0;i<n;i++)printf("%d
    ",ans[i]);
    }    
    •   应用:

      Mokia

      这是一个三维偏序问题,我们把时间当做第一维,x当做第二维,y当做第三维。

      按照处理三维偏序的思路,我们先按(t>x>y)排序,对x进行归并,对y进行树状数组。

      但是此题的查询比较烦:它查询的是矩阵前缀和。因此对每个查询,我们要处理四个区间的前缀和。这里我把一次询问拆成了四次询问。

      具体细节看code:

      

    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<cstdio>
    #define lowbit(x) (x&-x)
    using namespace std;
    const int W=2000010;
    const int Q=200010;
    struct node
    {
        int id,x,y,t,sum;//id为时间,t为类型(add还是query),t=0,sum为添加的值, 
                         //t=1,sum为返回值 
    }e[Q],t[Q];
    int cnt;
    int tarr[W];
    
    inline int read()
    {
        int x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
        return x*f;
    }
    
    int w;
    
    bool cmp(node a,node b)
    {
        return a.id==b.id?(a.x==b.x?a.y<b.y:a.x<b.x):a.id<b.id;
    }
    
    bool cmp2(node a,node b)
    {
        return a.id<b.id;
    }
    
    void add(int pos,int val)
    {
        while(pos<=W)
        {
            tarr[pos]+=val;
            pos+=lowbit(pos);
        }
    }
    
    int query(int pos)
    {
        int res=0;
        while(pos)
        {
            res+=tarr[pos];
            pos-=lowbit(pos);
        }return res;
    }
    
    void CDQ(int l,int r)//CDQ分治和归并板子其实没啥本质区别 
    {
        if(l==r)return;
        int mid=(l+r>>1);
        CDQ(l,mid);CDQ(mid+1,r);
        int i=l,j=mid+1,k=l;//基本操作 
        while(i<=mid&&j<=r)
        {
            if(e[i].x<=e[j].x)//对第二维进行归并,如果满足条件,把第三维的贡献放到树状数组里 
            {
                if(e[i].t==0)add(e[i].y,e[i].sum);//此时t需要为0 
                t[k++]=e[i++];
            }
            else
            {
                if(e[j].t==1)e[j].sum+=query(e[j].y);//如果已经没有满足条件的x了,我们就进行统计 
                t[k++]=e[j++];//此时t需要为1 
            }
        }
        while(i<=mid)
        {
            if(e[i].t==0)
            add(e[i].y,e[i].sum);
            t[k++]=e[i++];
        }
        while(j<=r)
        {
            if(e[j].t==1)
            e[j].sum+=query(e[j].y);
            t[k++]=e[j++];
        }//统计剩下的 
        for(int i=l;i<=mid;i++)if(e[i].t==0)add(e[i].y,-e[i].sum);//必须要清空树状数组 
        for(int i=l;i<=r;i++)
        e[i]=t[i];
    }
    
    int main()
    {
        while(1)
        {
            int cid=read();
            if(cid==0)w=read();
            if(cid==1)
            {
                int x=read()+1,y=read()+1,z=read();
                e[++cnt]=(node){cnt,x,y,0,z}; //结构体直接读取 
            }
            if(cid==2)
            {
                int i=read(),j=read(),x=read()+1,y=read()+1;//x,y可能为0,树状数组会爆 
                e[++cnt]=(node){cnt,i,j,1,0};//因此它们都要加一,对结果无影响 
                e[++cnt]=(node){cnt,i,y,1,0};
                e[++cnt]=(node){cnt,x,j,1,0};
                e[++cnt]=(node){cnt,x,y,1,0};//一个询问拆成四个询问 
            }
            if(cid==3)break;
        }
        sort(e+1,e+1+cnt,cmp);//对第一维排序 
        CDQ(1,cnt);
        sort(e+1,e+1+cnt,cmp2);//查询之前一定要按时间排好序,因为已经按第二维归并了一遍 
        for(int i=1;i<=cnt;i++)
        {
            int ans=0;
            if(e[i].t==1)
            {
                int a=e[i].sum,b=e[i+1].sum,c=e[i+2].sum,d=e[i+3].sum;
                ans=a-b-c+d;printf("%d
    ",ans);//遇到查询,4个合并起来就是最终答案 
                i+=3;
            }
        }
    }

      二维偏序很好懂,三维偏序太难画,所以这里就不放图了

      完美撒花✿✿ヽ(°▽°)ノ✿

      ——Thranduil

  • 相关阅读:
    WPF之感触
    C# WinForm 给DataTable中指定位置添加列
    MyEclipse 8.6 download 官方下载地址
    将博客搬至CSDN
    Building Microservices with Spring Cloud
    Building Microservices with Spring Cloud
    Building Microservices with Spring Cloud
    Building Microservices with Spring Cloud
    Building Microservices with Spring Cloud
    Building Microservices with Spring Cloud
  • 原文地址:https://www.cnblogs.com/THRANDUil/p/11037651.html
Copyright © 2011-2022 走看看