• 偏序问题与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
    ", d[i]);
        return 0;
    }

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

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

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

  • 相关阅读:
    java Metaspace频繁FGC问题定位(转载)
    JVM内存模型详解(转载)
    56. Merge Intervals
    begin again
    55. Jump Game
    54. Spiral Matrix
    53. Maximum Subarray
    52. N-Queens II
    51. N-Queens
    1. 赋值运算符函数
  • 原文地址:https://www.cnblogs.com/Bhllx/p/9815994.html
Copyright © 2020-2023  润新知