偏序问题与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解决只能离线。。

 

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

 

posted @ 2018-10-19 13:44  薄荷凉了夏  阅读(352)  评论(1编辑  收藏  举报