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

    <后记>

  • 相关阅读:
    web服务器-Apache
    nginx优化
    nginx下载限速
    nginx-URL重写
    HDU 5358 First One 求和(序列求和,优化)
    HDU 5360 Hiking 登山 (优先队列,排序)
    HDU 5353 Average 糖果分配(模拟,图)
    UVALive 4128 Steam Roller 蒸汽式压路机(最短路,变形) WA中。。。。。
    HDU 5348 MZL's endless loop 给边定向(欧拉回路,最大流)
    HDU 5344 MZL's xor (水题)
  • 原文地址:https://www.cnblogs.com/Parsnip/p/10816330.html
Copyright © 2011-2022 走看看