• 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

  • 相关阅读:
    新开博客,随意写写
    HDU 3534
    HDU 4118
    HDU 4276
    HDU 3586
    HDU 4044
    windows浏览器访问虚拟机centos7开的rabbitmq,解决rabbitmq添加远程访问功能
    springboot+cache+redis缓存实例demo
    链表中倒数第K个节点
    反转链表
  • 原文地址:https://www.cnblogs.com/THRANDUil/p/11037651.html
Copyright © 2020-2023  润新知