zoukankan      html  css  js  c++  java
  • sss

    <更新提示>

    <第一次更新>

    <第二次更新>更新了三维偏序问题的拓展


    <正文>

    cdq分治

    (cdq)分治是一种由(IOI Au)选手(cdq)提出的离线分治算法,又称基于时间的分治算法。

    二维偏序问题

    这是(cdq)分治最早提出的时候解决的问题,大意为:给定(n)对二元组((a_{i},b_{i})),求(cnt_i=sum_{j=i+1}^n[a_j>a_i and b_j>b_i])

    这个和逆序对有点像,先将二元组按(a)为第一关键字排序,那么(a)就一定有序了,即求下标和数值((b))都比当前二元组((b))大的二元组数量,和逆序对相反,就是正序对

    逆序对有一个很高效的(O(nlog_2n))的求法,就是归并排序。当然,正序对也可以用归并排序做,其原理是类似的。

    其实,这就是(cdq)分治的原型了。从分治的角度考虑,(solve(l,r))对序列([l,r])的部分进行求解,首先我们将([l,r])分治为([l,mid])([mid+1,r])这两部分,这是可以递归求解的。那么,我们要考虑的就是分别在区间([l,mid])([mid+1,r])两边的元素构成的正序对数量。

    怎么求呢?归并就可以了。在把两边的元素归并排序的过程中,左边的二元组的第一维((a))显然还小于右边,但是在归并的过程中我们可以得知第二维(b)的关系,这样就方便统计答案了。

    所以,求解二维偏序问题的(cdq)分治算法就是以归并排序求逆序对为原型的。

    (Code:)((HLOJ)炉石传说)

    #include <bits/stdc++.h>
    using namespace std;
    const int N=200020;
    int n,ans[N];
    struct node{int a,b,id;};
    node c[N],cur[N];
    inline void input(void)
    {
        scanf("%d",&n);
        for (int i=1;i<=n;i++)
            scanf("%d%d",&c[i].a,&c[i].b) , c[i].id = i;
    }
    inline bool compare(node x,node y)
    {
        return x.a == y.a ? x.b < y.b : x.a < y.a;
    }
    inline void cdq(int l,int r)
    {
        if ( l == r ) return;
        int mid = l+r >> 1 , p = l , q = mid+1 , t = l-1;
        cdq(l,mid); cdq(mid+1,r);
        while ( p <= mid && q <= r )
        {
            if ( c[p].b <= c[q].b )
                cur[++t] = c[p++];
            if ( c[p].b > c[q].b )
                ans[ c[q].id ] += p-l , cur[++t] = c[q++];
        }
        while ( p <= mid ) cur[++t] = c[p++];
        while ( q <= r ) ans[ c[q].id ] += mid-l+1 , cur[++t] = c[q++];
        for (int i=l;i<=r;i++) c[i] = cur[i];
    }
    int main(void)
    {
        input();
        sort( c+1 , c+n+1 , compare );
        cdq(1,n);
        for (int i=1;i<=n;i++)
            printf("%d
    ",ans[i]);
        return 0;
    }
    

    二维偏序问题的拓展

    Describetion

    假设有一列数({A_i }(1 ≤ i ≤ n)) ,支持如下两种操作:

    (1)将(A_k)的值加(D)。((k),(D)是输入的数)

    (2) 输出(A_s+A_{s+1}+…+A_t)。((s)(t)都是输入的数,(S≤T))

    根据操作要求进行正确操作并输出结果。

    解析

    这个很明显是线段树/树状数组模板题吧,当然,我们是可以转换为二维偏序问题来解决的。

    我们将给出的指令分为两类:查询指令和修改指令。可以这样思考:对于一个查询指令,其结果等价于计算原始数列和之后一系列修改对该位置的影响。那么我们把它转换为二维偏序问题:二元组((a,b))代表在时刻(a),位置(b)进行的指令,我们要知道所有时刻(a)以前,位置(b)以前修改指令对查询指令的影响,这就类似于正序对了。

    重新定义分治函数(solve(l,r))为计算时间([l,r])中所有修改指令对查询指令的影响,和归并排序一样计算就可以了。

    敲代码时候的一点小技巧:将区间和查询([l,r])改为两个前缀和查询,其中一个为正影响,一个为负影响,这样就方便上述方法修改操作对查询操作影响的计算了。

    (Code:)((HLOJ)数列求和)

    #include <bits/stdc++.h>
    using namespace std;
    const int N=100020,M=150020;
    int n,m,cnt,tot;
    long long ans[M];
    struct order
    {
        int flag,pos;
        long long val;
        bool operator < (const order &t)const
        {
            return pos == t.pos ? flag < t.flag : pos < t.pos;
        }
    }a[N+2*M],cur[N+2*M];
    inline void input(void)
    {
        scanf("%d",&n);
        for (int i=1;i<=n;i++)
        {
            long long val;scanf("%lld",&val);
            a[++cnt] = (order){1,i,val};
        }
        scanf("%d",&m);
        for (int i=1;i<=m;i++)
        {
            char op[5];long long k1,k2;
            scanf("%s%lld%lld",op,&k1,&k2);
            if (op[0]=='A') a[++cnt] = (order){1,k1,k2};
            if (op[0]=='S') a[++cnt] = (order){2,k2,++tot} , a[++cnt] = (order){3,k1-1,tot};
        }
    }
    inline void cdq(int l,int r)
    {
        if ( l == r ) return;
        int mid = l+r >> 1 , p = l , q = mid+1 , t = l-1 , sum = 0;
        cdq(l,mid); cdq(mid+1,r);
        while ( p <= mid && q <= r )
        {
            if ( a[p] < a[q] ) 
            {
                if ( a[p].flag == 1 ) sum += a[p].val;
                cur[++t] = a[p++];
            }
            else
            {
                if ( a[q].flag == 2 ) ans[ a[q].val ] += sum;
                if ( a[q].flag == 3 ) ans[ a[q].val ] -= sum;
                cur[++t] = a[q++];
            }
        }
        while ( p <= mid ) cur[++t] = a[p++];
        while ( q <= r )
        {
            if ( a[q].flag == 2 ) ans[ a[q].val ] += sum;
            if ( a[q].flag == 3 ) ans[ a[q].val ] -= sum;
            cur[++t] = a[q++];
        }
        for (int i=l;i<=r;i++) a[i] = cur[i];
    }
    int main(void)
    {
        input();
        cdq(1,cnt);
        for (int i=1;i<=tot;i++)
            printf("%lld
    ",ans[i]);
        return 0;
    }
    

    三维偏序

    二维偏序的问题同样有升级版,只不过换成了三元组而已。

    同样地,我们先排序保证第一维的有序性,然后对第二维进行(cdq)分治,只不过在更新的时候出了点问题:满足前两维(a,b)有序是还需满足第三维(c)有序,那么我们在值域范围上建立一个树状数组,统计每个(c)值的出现次数,然后查询前缀和就可以了。

    回顾二维偏序问题,如果不用(cdq)分治,树状数组也能直接解决,在这题中,如果不用(cdq)分治,可以用树状数组套平衡树做。其实,(cdq)分治的好处就在于顶替了一层数据结构,这样就可以大大减少代码量和时间常数。

    (Code:)((BZOJ)陌上花开)

    #include <bits/stdc++.h>
    using namespace std;
    const int N=102000,K=220000;
    int n,k,ans[N],bucket[N];
    struct BIT
    {
        int p[K];
        inline int lowbit(int x){return x&(-x);}
        inline void insert(int pos,int val)
        {
            for (;pos<=k;pos+=lowbit(pos))
                p[pos] += val;
        }
        inline int query(int pos)
        {
            int res = 0;
            for (;pos;pos-=lowbit(pos))
                res += p[pos];
            return res;
        }  
    }tree;
    struct flower{int a,b,c,id;};
    flower f[N],cur[N];
    inline bool compare(flower p1,flower p2)
    {
        if ( p1.a ^ p2.a ) return p1.a < p2.a;
        if ( p1.b ^ p2.b ) return p1.b < p2.b;
        return p1.c < p2.c;
    }
    inline bool equal(flower p1,flower p2)
    {
        return p1.a == p2.a && p1.b == p2.b && p1.c == p2.c;
    }
    inline int read(void)
    {
        int x = 0 , w = 0; char ch=' ';
        while (!isdigit(ch)) w |= ch=='-' , ch = getchar();
        while (isdigit(ch)) x = (x<<1) + (x<<3) + (ch^48) , ch = getchar();
        return w ? -x : x; 
    }
    inline void input(void)
    {
        n = read() , k = read();
        for (register int i=1;i<=n;i++)
            f[i].a = read() , f[i].b = read() , f[i].c = read() , f[i].id = i;
    }
    inline void init(void)
    {
        flower t;int cnt = 1;
        for (register int i=n;i>=1;i--)
            if (equal(t,f[i])) 
                ans[ f[i].id ] += cnt , cnt++;
            else t = f[i] , cnt = 1;
    }
    inline void cdq(int l,int r)
    {
        if ( l == r ) return;
        int mid = l+r >> 1 , p = l , q = mid+1 , t = l-1 ;
        cdq(l,mid); cdq(mid+1,r);
        while ( p <= mid && q <= r )
        {
            if ( f[p].b <= f[q].b )
            {
                tree.insert(f[p].c,1);
                cur[++t] = f[p++];
            }
            if ( f[p].b > f[q].b )
            {
                ans[ f[q].id ] += tree.query(f[q].c);
                cur[++t] = f[q++];
            }
        }
        while ( q <= r ) ans[ f[q].id ] += tree.query(f[q].c) , cur[++t] = f[q++];
        for (register int i=l;i<p;i++) tree.insert(f[i].c,-1);
        while ( p <= mid ) cur[++t] = f[p++];
        for (register int i=l;i<=r;i++) f[i] = cur[i]; 
    }
    int main(void)
    {
        input();
        sort( f+1 , f+n+1 , compare );
        init();
        cdq(1,n);
        for (register int i=1;i<=n;i++)
            bucket[ ans[i] ]++;
        for (register int i=0;i<n;i++)
            printf("%d
    ",bucket[i]);
        return 0;
    }
    

    三维偏序问题的拓展

    Describetion

    有一个(N*N)矩阵,给出一系列的修改和询问,修改是这样的:将((x,y))中的数字加上(k),而询问是这样的:求((x_1,y_1))((x_2,y_2))这个子矩阵内所有数字的和。

    解析

    这个和二维偏序问题的拓展相似,将区间求和推广为了矩阵求和。但是其本质还是一样的,我们将一根矩阵求和的询问拆为四个二维前缀和的查询。对于二维前缀和的查询和单点的修改,我们将其转换为三维偏序问题:三元组((t,x,y))代表在时刻(t),位置((x,y))执行的指令,显然,当修改操作((t',x',y'))对操作操作((t,x,y))有影响时,(t'<t,x'leq x,y'leq y),这就是经典的三维偏序问题。

    我们结合上文"数列求和","陌上花开"两题的思路,利用(cdq)分治和树状数组就可以轻松的实现本题了。

    (Code:)((HLOJ)好朋友的题)

    #include <bits/stdc++.h>
    using namespace std;
    const int N=2000020,M=800020;
    int n,m,cnt,tot,ans[M];
    struct BIT
    {
        int p[N];
        inline int lowbit(int x){return x&(-x);}
        inline void insert(int pos,int val)
        {
            for (;pos<=n;pos+=lowbit(pos))
                p[pos] += val;
        }
        inline int query(int pos)
        {
            int res = 0;
            for (;pos;pos-=lowbit(pos))
                res += p[pos];
            return res;
        }  
    }tree;
    struct order
    {
        int flag,x,y,val;
        bool operator < (order p)
        {
            if ( x ^ p.x ) return x < p.x;
            if ( y ^ p.y ) return y < p.y;
            return flag < p.flag;
        }
    }a[M],cur[M];
    inline void input(void)
    {
        int op,x1,y1,x2,y2,val;
        scanf("%d",&n);
        while (true)
        {
            scanf("%d",&op);
            if ( op == 1 )
            {
                scanf("%d%d%d",&x1,&y1,&val);
                a[++cnt] = (order){1,x1,y1,val};
            }
            if ( op == 2 )
            {
                scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
                a[++cnt] = (order){2,x2,y2,++tot};
                a[++cnt] = (order){2,x1-1,y1-1,tot};
                a[++cnt] = (order){3,x1-1,y2,tot};
                a[++cnt] = (order){3,x2,y1-1,tot};
            }
            if ( op == 3 ) break;
        }
    }
    inline void cdq(int l,int r)
    {
        if ( l == r ) return;
        int mid = l+r >> 1 , p = l , q = mid+1 , t = l-1;
        cdq(l,mid); cdq(mid+1,r);
        while ( p <= mid && q <= r )
        {
            if ( a[p] < a[q] )
            {
                if ( a[p].flag == 1 ) tree.insert(a[p].y,a[p].val);
                cur[++t] = a[p++];
            }
            else
            {
                if ( a[q].flag == 2 ) ans[ a[q].val ] += tree.query(a[q].y);
                if ( a[q].flag == 3 ) ans[ a[q].val ] -= tree.query(a[q].y);
                cur[++t] = a[q++];
            }
        }
        while ( q <= r )
        {
            if ( a[q].flag == 2 ) ans[ a[q].val ] += tree.query(a[q].y);
            if ( a[q].flag == 3 ) ans[ a[q].val ] -= tree.query(a[q].y);
            cur[++t] = a[q++];
        }
        for (int i=l;i<p;i++) 
            if ( a[i].flag == 1 )
                tree.insert(a[i].y,-a[i].val);
        while ( p <= mid ) cur[++t] = a[p++];
        for (int i=l;i<=r;i++) a[i] = cur[i];
    }
    int main(void)
    {
        input();
        cdq(1,cnt);
        for (int i=1;i<=tot;i++)
            printf("%d
    ",ans[i]);
        return 0;
    }
    

    <后记>

  • 相关阅读:
    一文搞懂字符集
    机器视觉之eVision
    PID调节
    激光切割质量主要影响因素
    155. 最小栈
    111.二叉树最小深度
    110. 平衡二叉树
    108.将有序数组转换为二叉搜索树
    107. 二叉树的层次遍历 II
    104. 二叉树的最大深度
  • 原文地址:https://www.cnblogs.com/Parsnip/p/10816330.html
Copyright © 2011-2022 走看看