对CDQ分治的一些见解

版权声明:本文为博主原创文章,转载请注明源网址blog.csdn.net/leo_h1104 https://blog.csdn.net/Leo_h1104/article/details/61208803

引述

序列中,对于每个i,统计满足a[j]<a[i]的j的数量,我们可以将序列按a排序来解决
若状态扩展到二维,对于每个二元组(a[i],b[i])求满足a[j]<a[i],b[j]<b[i]的j的数量,则可以将所有二元组按a排序,按下标从小到大扫描序列,依次将元素插入树状数组,再用BIT查询此点前缀和即可
但是若每次操作插入一个元素,或者询问一个元素,那么时间也会成为两个操作互相影响的变量,故不能随意排序了,这时我们就需要CDQ分治来对这个三维问题求解

做法

我们可以先将操作序列的时间t记录下来,然后按照a排序,之后便有两种不同的做法

做法1-快排法

每次分治到的区间[l,r],可以保证所有的元素时间也在[l,r]内。令mid=(l+r)/2,按a升序扫描区间,依次将其中所有时间小于mid的修改和时间大于mid的查询在树状数组上完成,这样事先的排序保证了a的顺序,分治解决了t的顺序,树状数组则解决了b的。然而这里只处理了小于mid的修改对大于mid的询问的影响,故还要将区间按时间划分为[l,mid]和[mid+1,r]两段,递归处理。
回顾以上过程,我们每次分治都把区间以mid为标准分为左右两半,然后对左右进行递归,这不正是快速排序的过程吗?因此我们将它命名为快排法

做法2-归并法

既然CDQ的实质是在将数组排序的过程中累计答案,那么除了快排有没有其他支持分治的排序方式呢?答案是肯定的。
分治排序与快排不同,要先递归左右两半,后对整个区间进行归并。这样,在递归过后,整个区间分为左右两部分,每一部分内部按t排好序(由排序性质),左面的任意一个a小于右面任意一个a。于是我们可以在左面和右面分别设置指针,按照类似归并的方法,处理左面的修改对右面询问的影响,只要保证按时间顺序操作即可。最后在将区间二路归并,保证其排序性质。
与快排法相比,这种方法不需要严格保证t和区间下标范围相同,但是归并细节较多,可能在代码量上有较大差距。

实现

其实CDQ分治能解决各种三维影响问题,不要局限于时间t。但是若选择方法1,则必须有一维做为时间(即值域为1~n)保证分治区间吻合。
实现上有一点需要注意,每次分治都需要一个空的树状数组来维护答案,但是每次都清空数组会使用O(n2)的时间。所以我们在每次递归前都把本次所有修改操作反向做一遍,这样的时间效率是O(nlog2n)。
这里只给出分治部分的代码,结构体声明、排序和树状数组请读者自行完成

快排法代码

void cdq(int l=1,int r=cnto)
{
    int p1=l;
    int p2=mid+1;
    for(int i=l;i<=r;i++) 
    {
        if(o[i].t<=mid&&!o[i].qid)
            bit.add(o[i].b,1);
        if(o[i].t>mid&&o[i].qid) 
            ans[o[i].qid]+=bit.getsum(o[i].b);
    }
    for(int i=l;i<=r;i++)
        if(o[i].t<=mid&&!o[i].qid)
            bit.add(o[i].b,-1);
    for(int i=l;i<=r;i++) 
        if(o[i].t<=mid) aux[p1++]=o[i];
        else aux[p2++]=o[i];
    for(int i=l;i<=r;i++) o[i]=aux[i];
    if(l<mid)cdq(l,mid);
    if(mid+1<r)cdq(mid+1,r);
}

归并法代码

声明:此份代码非博主本人完成,仅供参考和理解

void solve(int l,int r)
{
    int mid;
    int f1;
    int f2;
    if(l==r)
        return;
    mid=(l+r)>>1;
    solve(l,mid);
    solve(mid+1,r);
    for(f1=l,f2=mid+1;(f1<=mid)&&(f2<=r);)
        if(szp[f1].x<=szp[f2].x)
        {
            if(szp[f1].l==0)
                adds(szp[f1].y,szp[f1].d);
            f1++;
        }
        else
        {
            if(szp[f2].l!=0)
                sza[szp[f2].d]+=gets(szp[f2].y)*szp[f2].l;
            f2++;
        }
    for(;f2<=r;f2++)
        if(szp[f2].l!=0)
            sza[szp[f2].d]+=gets(szp[f2].y)*szp[f2].l;
    for(f1--;f1>=l;f1--)
        if(szp[f1].l==0)
            adds(szp[f1].y,-szp[f1].d);
    totn=l;
    for(f1=l,f2=mid+1;(f1<=mid)&&(f2<=r);)
        if(szp[f1].x<=szp[f2].x)
            szn[totn++]=szp[f1++];
        else
            szn[totn++]=szp[f2++];
    for(;f1<=mid;f1++)
        szn[totn++]=szp[f1];
    for(;f2<=r;f2++)
        szn[totn++]=szp[f2];
    for(f1=l;f1<=r;f1++)
        szp[f1]=szn[f1];
    return;
}

完结撒花

阅读更多

我对高程的一些见解

03-26

02年过的高程,在读研究生。但不是系科的。rn我见到本版很多同仁讨论考高程到底能带来什么好处,对于此,我有一些自己的观点如下:rn1。我从大一开始学C语言,当然是作为兴趣,那时好像不知道有高程这回事,自知道有二级。高年级有人过了三级,我都会羡慕好半天。大二开始辅修计算机专业,也是兴趣是然,没能学完,所以大四没拿到证书,但我通过自学把哪些课程都学了。不过感觉学的很浅 ,没有系统。rn2。大四那时遇到了几个爱好计算机的朋友,都不是系科的,但大家有共同的喜好,第一回听到有高程这个东西,好在免试读研,那我就把哪些课程好好看看把。就报了高程,不过感觉没有什么压力,过了也好,不过也罢。暑假开始把书看了,很认真,编程序也很多。rn3。总的说来,我感觉考式是次要的,但我认为通过考试你可以系统的学一些东西,计算机这个行业本来就是很看重能力的,我在考试以前也写了很多程序,编的一些代码在一些网站上也被下载过,但我认为自己那时很不看重数据结构等最基础的东西,但通过考试,我意识到了基础的重要性。我相,这就是这个考试给我的吧,至于证书,我想那是给自己看的,证明你那个时候在干了些什么,其他没什么了。rn以上存数个人意见

没有更多推荐了,返回首页