对CDQ分治的一些见解

10 篇文章 1 订阅
1 篇文章 0 订阅

引述

序列中,对于每个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(nlog 2 n)。
这里只给出分治部分的代码,结构体声明、排序和树状数组请读者自行完成

快排法代码

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;
}

完结撒花

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值