偏序问题与CDQ分治

NOIP前这么一点点时间,我还是花了一晚上简单学了一下CDQ分治解决偏序问题。

不过暂时来讲,还是只会比较裸一点的,它的精髓还是没有完全掌握。以后有时间会完善吧。。

但是就算只会很裸很裸的,也还是想讲讲。

 

首先二维偏序板子:

a,b两个序列,长度都为n,求有多少个数对(i,j)满足:a[i]<a[j],b[i]<b[j]。(值得注意的是这里的i,j是没规定大小的)

可以看到,这种两个序列被要求满足一定大小关系的问题就是二维偏序问题。。

那么的话,怎么求呢??

可以这么考虑,如果我们只有一维是不是直接求个正序对就行了。

现在有两维的话,我们考虑如果有一个维度已经有序的话,那么就只要考虑第二维满足条件的就好了。

所以就是按第一维排序,第二维正序对就好了,至于归并还是树状数组就随意了。。

代码的话相信强大的您不需要吧 ~O(∩_∩)O~

 

那么三维偏序呢??

可以这么看吧,其实就是在二维基础上,多一个序列,多一个限制条件而已。

那么的话我们还是类似的,第一维排序解决。

还剩下两维怎么搞??

没问题。。我们直接归并排序刚第二维。

考虑合并时,右区间每个数都满足第二维大于左区间,同时第一维也是满足条件,那么

我们只要考虑对于每一个右区间里的数,左区间里有多少满足第三维条件的就好了。。

这样的话我们同样可以建立权值树状数组,维护的是左区间第三维的一个信息,

具体一点就是每从左边来一个数,就在对应位置上+1,右边来一个数,就直接查询比他小的有多少就好了。

至此,三维偏序基本可以很好的解决了。

时间复杂度O(nlog2),空间复杂度O(值域)。

 

但是,如果是值域很大呢?你也许还可以离散化一下。

那如果是n维偏序呢,莫非树套树套树套树。。。??也许对dalao仅仅是复杂点,对我的话就直接GG了。。

 

那么这就可以用到来CDQ分治有效代替高级复杂的数据结构了。

什么是CDQ分治?其实归并排序求逆序对就有CDQ分治的思想。

我的认为大概就是,分别处理子问题后,考虑前一个子问题对后一个子问题造成的影响或产生的贡献,从而得到答案。

就拿三位偏序为例吧,我们用归并套归并,也就是CDQ套CDQ来解决。

同样的,我们只要考虑对于每一个右区间里的数,左区间里有多少满足第三维条件的就好了。

这其实应该是CDQ分治最擅长解决的问题。

我们考虑把每一个数是从左还是右来(第二维排序下),然后在先合并后(保证第二维在之后的归并中不受影响),继续第三维的归并。

那么考虑对于第三维的归并,我们已知一些什么?

最重要的就是我们记录了他在第二维意义下来自左边还是右边。。

那么我们对于每一个第三维排序下,

每从左边来一个数,看他第二维排序是否来自左边,是的话cnt++,

说明它可以对后面的  每一个(第三维排序下)来自右区间,(第二维排序下)来自右区间的数造成1的贡献。

而对于来自右区间的数只要满足上述条件,Ans+=cnt就好了。

然后贴下三维偏序模板(陌上花开)代码吧

#include <bits/stdc++.h>
using namespace std;
inline int gi () {
    int x=0, w=0; char ch=0;
    while (! (ch>='0' && ch<='9') ) {
        if (ch=='-') w=1;
        ch=getchar ();
    }
    while (ch>='0' && ch<='9') {
        x= (x<<3) + (x<<1) + (ch^48);
        ch=getchar ();
    }
    return w?-x:x;
}

const int N=1e5+10;

int n,k,d[N],Ans[N];

struct Seq {
    bool Tag;
    int *Ans;
    int a, b, c;
    bool operator == (const Seq &s) const {
        return a==s.a && b==s.b && c==s.c;
    }
}seq[N],sB[N],sC[N];
inline bool CMP (Seq x, Seq y) {
    return  x.a<y.a || (x.a==y.a && x.b<y.b) || (x.a==y.a && x.b==y.b && x.c<y.c);
}

void Merge2 (int l, int r) {
    if (l==r) return;
    int Mid= (l+r) >>1;
    Merge2 (l, Mid); Merge2 (Mid+1, r);
    for (int i=l, j=Mid+1, id=l, cnt=0;id<=r;++id) {
        if ( (j>r || sB[i].c<=sB[j].c) && i<=Mid)
            sC[id]=sB[i++], cnt+=sC[id].Tag;
        else {
            sC[id]=sB[j++];
            if (!sC[id].Tag) *sC[id].Ans+=cnt;
        }
    }
    for (int i=l;i<=r;++i) sB[i]=sC[i];
}

void Merge1 (int l, int r) {
    if (l==r) return;
    int Mid= (l+r) >>1;
    Merge1 (l, Mid); Merge1 (Mid+1, r);
    for (int i=l, j=Mid+1, id=l;id<=r;++id) {
        if ( (j>r || seq[i].b<=seq[j].b ) && i<=Mid)
            sB[id]=seq[i++], sB[id].Tag=1;
        else sB[id]=seq[j++], sB[id].Tag=0;
    }
    for (int i=l;i<=r;++i) seq[i]=sB[i];
    Merge2 (l, r);
}

int main () {
    n=gi (), k=gi ();
    for (int i=1;i<=n;++i) {
        seq[i].a=gi (), seq[i].b=gi (), seq[i].c=gi ();
        seq[i].Ans=&Ans[i], Ans[i]=0;        
    }
    sort (seq+1, seq+n+1, CMP);
    for (int i=n-1;i;--i)
        if (seq[i]==seq[i+1]) *seq[i].Ans=*seq[i+1].Ans+1;
    Merge1 (1, n);
    for (int i=1;i<=n;++i) d[Ans[i]]++;
    for (int i=0;i<n;++i) printf ("%d\n", d[i]);
    return 0;
}

其实可以发现这样的话n维偏序也只要多打几个归并,免去了高维下复杂的数据结构,还是挺好用的。

缺点可能就是比树状数组什么的常数大些吧  还有就是很多拓展问题用CDQ解决只能离线。。

 

大概我就搞了这么点东西吧,东西少,话却说了蛮多。另外的话感谢一下尻哥的友情帮助啦。

 

转载于:https://www.cnblogs.com/Bhllx/p/9815994.html

### CDQ分治法的基本概念 CDQ分治是一种基于分治思想的算法策略,最初由陈丹琦引入国内算法竞赛领域,因此被称为CDQ分治。该方法的核心思想是将问题划分为若干子问题,并递归地解决这些子问题。在解决子问题的同时,处理左半部分对右半部分的影响,从而逐步构建最终解[^1]。 CDQ分治的关键特征在于其递归结构:首先递归处理左半区间和右半区间的子问题,随后处理左区间对右区间的影响。这种策略通常利用排序来制造单调性,从而降低计算复杂度[^2]。 ### CDQ分治法的工作原理 CDQ分治的工作流程可以分为以下三个步骤: 1. **划分**:将原始问题划分为两个子问题,通常是对数组进行二分,分别处理左半区间和右半区间。 2. **处理**:计算左半区间对右半区间的影响。这一过程通常涉及对数据进行排序,以利用单调性减少重复计算[^3]。 3. **合并**:递归地处理右半区间的问题,并将结果整合。 以归并排序求逆序对为例,在合并两个子区间的过程中,需要计算左边区间对右边区间的影响。具体来说,当从右子区间中取出一个元素时,统计左边区间中比该元素大的元素数量,从而得到逆序对的个数。这一过程体现了CDQ分治的核心思想[^3]。 ### CDQ分治法的应用场景 CDQ分治广泛应用于解决多偏序问题,例如二偏序、三偏序等。对于二偏序问题,可以通过CDQ分治结合排序和树状数组来高效求解。在处理三偏序问题时,CDQ分治通常树状数组结合,以达到 $ O(n \log^2 n) $ 的时间复杂度[^5]。 此外,CDQ分治也常用于动态规划优化问题。例如,在求解最长递增子序列问题时,可以利用CDQ分治处理条件约束,例如 $ f_i = \max\{f_j + 1 \mid j < i, r_j \le a_i, a_j \le l_i\} $,其中 $ f_i $ 表示以第 $ i $ 个元素为结尾的最长子序列长度[^4]。 ### CDQ分治法的实现示例 以下是一个基于CDQ分治的伪代码示例,用于处理二偏序问题: ```python def cdq_divide(l, r): if l == r: return mid = (l + r) // 2 cdq_divide(l, mid) cdq_divide(mid + 1, r) # 处理左区间对右区间的影响 # 例如:统计左区间中满足条件的元素对右区间的影响 # ... # 合并两个子区间并排序 # ... ``` 在具体实现中,通常需要结合归并排序的思想,以确保数据在分治过程中保持有序,从而提高效率[^5]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值