zoukankan      html  css  js  c++  java
  • cdq分治

    【cdq分治】

    数据结构问题:

    • 维护某种数据结构,使其对一系列操作(查询/修改)一次快速做出响应。
    • “时间轴”:操作的顺序

    如果需要实时进行响应,称为【在线算法】,在查询时立即回答,然后再继续进行下一次操作;否则需要预先知晓整个操作序列,经过一系列计算,批量回答所有查询,称为【离线算法】。

    另外,根据两种操作在时间轴上分布的不同,可以再加以区分。边修改边查询,一般称为【动态问题】;不包含修改,或者所有查询都在修改之后,称为【静态问题】。

    在一些离线的动态问题中,【cdq分治】提供了一种付出log(M)时间,将其转化成若干静态子问题的离线求解框架,其中M = 操作数量。有时也称cdq分治为【对于时间的分治算法】。

    cdq分治的原理基于以下事实:

    • 对于每个“查询”操作,其结果ans[i] = [1,i-1]中所有修改对其造成影响的叠加(这里的“叠加”需要能够比较方便的维护,例如sum/min/max等)

    定义solve(l,r)为:对于第k个操作,k∈[l,r],若其为查询操作,则计算[l,k-1]中的修改对ans[i]造成的影响。设 mid = (l+r)/2,solve(l,r)的执行步骤如下:

    1. 递归计算solve(l,mid);
    2. 计算[l,mid]中所有的修改对[mid+1,r]中所有查询所造成的影响;
    3. 递归计算solve(mid+1,r)

    【P3810 【模板】三维偏序(陌上花开)】https://www.luogu.com.cn/problem/P3810

    题意:有 n 个元素,第 i 个元素有 a[i]/b[i]/c[i] 三个属性,对于每个元素求 f[i] = “满足 a[j]<=a[i] && b[j]<=b[i] && c[j]<=c[i] 的 j 数量,j≠i”

    众所周知,二维偏序问题的解决方法是树状数组(1个log)。三维偏序问题可以看成“动态”的二维偏序问题,b,c看成2维,a看成“时间轴”。

    CDQ分治,第1维a(分治),第2维b(排序),第3维c(树状数组单点修改/前缀求和),复杂度2个log。注意先按a排序后将a赋值为rk[a]。

    #pragma GCC optimize(3,"Ofast","inline")//O3优化
    #pragma GCC optimize(2)//O2优化
    #include <algorithm>
    #include <string>
    #include <cstring>
    #include <vector>
    #include <map>
    #include <stack>
    #include <set>
    #include <queue>
    #include <cmath>
    #include <cstdio>
    #include <iomanip>
    #include <ctime>
    #include <bitset>
    #include <cmath>
    #include <sstream>
    #include <iostream>
    
    #define LL long long
    #define ls nod<<1
    #define rs (nod<<1)+1
    #define pii pair<int,int>
    #define mp make_pair
    #define pb push_back
    #define INF 0x3f3f3f3f
    #define max(a,b) (a>b?a:b)
    #define min(a,b) (a<b?a:b)
    
    const double eps = 1e-10;
    const int maxn = 2e5 + 10;
    const int mod = 10;
    
    int sgn(double a){return a < -eps ? -1 : a < eps ? 0 : 1;}
    using namespace std;
    
    int n,k;
    int c[maxn],cnt[maxn];
    
    struct node {
        int a,b,c,cnt,id,ans;
    }s1[maxn],s2[maxn];
    
    bool cmpa(node n1,node n2) {
        if (n1.a == n2.a) {
            if (n1.b == n2.b)
                return n1.c < n2.c;
            else
                return n1.b < n2.b;
        }
        else
            return n1.a < n2.a;
    }
    
    bool cmpb(node n1,node n2) {
        if (n1.b == n2.b)
            return n1.c < n2.c;
        else
            return n1.b < n2.b;
    }
    
    
    int lowbit(int x) {
        return x & (-x);
    }
    
    void change(int x,int dx) {
        while (x <= k) {
            c[x] += dx;
            x += lowbit(x);
        }
    }
    
    int getsum(int x) {
        int s = 0;
        while (x) {
            s += c[x];
            x -= lowbit(x);
        }
        return s;
    }
    
    void cdq(int l,int r) {
        if (l == r)
            return ;
        int mid = (l + r) / 2;
        cdq(l,mid);
        cdq(mid+1,r);
        sort(s2+l,s2+mid+1,cmpb);
        sort(s2+mid+1,s2+r+1,cmpb);
        int i,j = l;
        for (i = mid + 1;i <= r;i++) {
            while (s2[i].b >= s2[j].b && j <= mid) {
                change(s2[j].c,s2[j].cnt);
                j++;
            }
            s2[i].ans += getsum(s2[i].c);
        }
        for (i = l;i < j;i++)
            change(s2[i].c,-s2[i].cnt);
    }
    int main() {
        cin >> n >> k;
        for (int i = 1;i <= n;i++) {
            cin >> s1[i].a >> s1[i].b >> s1[i].c;
        }
        sort(s1+1,s1+1+n,cmpa);
        int v = 0;
        int cc = 0;
        for (int i = 1;i <= n;i++) {
            v++;
            if (s1[i].a != s1[i+1].a || s1[i].b != s1[i+1].b || s1[i].c != s1[i+1].c) {
                cc++;
                s2[cc].a = s1[i].a;
                s2[cc].b = s1[i].b;
                s2[cc].c = s1[i].c;
                s2[cc].cnt = v;
                s2[cc].id = cc;
                v = 0;
            }
        }
        cdq(1,cc);
        for (int i = 1;i <= cc;i++) {
            cnt[s2[i].ans+s2[i].cnt-1] += s2[i].cnt;
        }
        for (int i = 0;i < n;i++)
            cout << cnt[i] << endl;
        return 0;
    }

    【P4093 [HEOI2016/TJOI2016]序列】https://www.luogu.com.cn/problem/P4093

    设i这个位置的原值=a[i], 最小值=a0[i], 最大值=a1[i]

    那么在j,i在选出子序列中相邻的条件是:

    1. j<i
    2. a[j]<=a0[i]
    3. a1[j]<=a[i]

    三维偏序LIS,用CDQ分治+树状数组解决。可以修改1-询问1-修改2-询问2这样预排好;更好的方法是[l,mid]中的修改按a[j]排序,[mid+1,r]中的询问按a0[i]排序,双指针保证在询问i前进行了所有满足a[j]<=a0[i]的修改。

    #pragma GCC optimize(3,"Ofast","inline")//O3优化
    #pragma GCC optimize(2)//O2优化
    #include <algorithm>
    #include <string>
    #include <cstring>
    #include <vector>
    #include <map>
    #include <stack>
    #include <set>
    #include <queue>
    #include <cmath>
    #include <cstdio>
    #include <iomanip>
    #include <ctime>
    #include <bitset>
    #include <cmath>
    #include <sstream>
    #include <iostream>
    
    #define LL long long
    #define ls nod<<1
    #define rs (nod<<1)+1
    #define pii pair<int,int>
    #define mp make_pair
    #define pb push_back
    #define INF 0x3f3f3f3f
    #define max(a,b) (a>b?a:b)
    #define min(a,b) (a<b?a:b)
    
    const double eps = 1e-10;
    const int maxn = 2e5 + 10;
    const int mod = 10;
    
    int sgn(double a){return a < -eps ? -1 : a < eps ? 0 : 1;}
    using namespace std;
    
    int n,m;
    int c[maxn],dp[maxn];
    
    struct node {
        int v,l,r,id;
    }s[maxn];
    
    bool cmpv(node n1,node n2) {
        return n1.v < n2.v;
    }
    bool cmpl(node n1,node n2) {
        return n1.l < n2.l;
    }
    bool cmpid(node n1,node n2) {
        return n1.id < n2.id;
    }
    
    int lowbit(int x) {
        return x & (-x);
    }
    
    void change(int x,int dx) {
        while (x <= n) {
            c[x] = max(c[x],dx);
            x += lowbit(x);
        }
    }
    
    void clear(int x) {
        while (x <= n) {
            c[x] = 0;
            x += lowbit(x);
        }
    }
    
    int getsum(int x) {
        int s = 0;
        while (x) {
            s = max(s,c[x]);
            x -= lowbit(x);
        }
        return s;
    }
    
    void cdq(int l,int r) {
        if (l == r)
            return ;
        int mid = (l + r) / 2;
        cdq(l,mid);
        sort(s+l,s+mid+1,cmpv);
        sort(s+mid+1,s+r+1,cmpl);
        int i,j = l;
        for (i = mid + 1;i <= r;i++) {
            while (j <= mid && s[j].v <= s[i].l) {
                change(s[j].r,dp[s[j].id]);
                j++;
            }
            dp[s[i].id] = max(dp[s[i].id],getsum(s[i].v)+1);
        }
        for (i = l;i < j;i++)
            clear(s[i].r);
        sort(s+mid+1,s+r+1,cmpid);
        cdq(mid+1,r);
    }
    
    int main() {
        cin >> n >> m;
        for (int i = 1;i <= n;i++) {
            cin >> s[i].v;
            s[i].l = s[i].v;
            s[i].r = s[i].v;
            s[i].id = i;
        }
        for (int i = 1;i <= m;i++) {
            int x,y;
            cin >> x >> y;
            s[x].l = min(s[x].l,y);
            s[x].r = max(s[x].r,y);
        }
        for (int i = 1;i <= n;i++)
            dp[i] = 1;
        cdq(1,n);
        int Max = 0;
        for (int i = 1;i <= n;i++) {
            Max = max(Max,dp[i]);
        }
        cout << Max << endl;
        return 0;
    }

    【P4390 [BOI2007]Mokia 摩基亚】 https://www.luogu.com.cn/problem/P4390

    题意:维护一个二维平面,动态支持单点加/矩阵求和。

    众所周知,本题的静态版本(所有询问都在修改操作之后进行)可以使用 扫描线 + 树状数组 在1个log时间内解决。

    直接套用cdq分治,第1维时间(分治),第2维x(排序),第3维y(树状数组单点修改/区间求和),复杂度2个log。

    注意左边界的询问要--x,右边界的询问要在相同x的修改之后进行。

    #pragma GCC optimize(3,"Ofast","inline")//O3优化
    #pragma GCC optimize(2)//O2优化
    #include <algorithm>
    #include <string>
    #include <cstring>
    #include <vector>
    #include <map>
    #include <stack>
    #include <set>
    #include <queue>
    #include <cmath>
    #include <cstdio>
    #include <iomanip>
    #include <ctime>
    #include <bitset>
    #include <cmath>
    #include <sstream>
    #include <iostream>
    
    #define LL long long
    #define ls nod<<1
    #define rs (nod<<1)+1
    #define pii pair<int,int>
    #define mp make_pair
    #define pb push_back
    #define INF 0x3f3f3f3f
    #define max(a,b) (a>b?a:b)
    #define min(a,b) (a<b?a:b)
    
    const double eps = 1e-10;
    const int maxn = 2e6 + 10;
    const int mod = 10;
    
    int sgn(double a){return a < -eps ? -1 : a < eps ? 0 : 1;}
    using namespace std;
    
    int n,cnt;
    int c[maxn];
    
    struct node {
        int tim,x,y,v,id ,op;
    }s[maxn];
    
    bool cmptim(node n1,node n2) {
        return n1.tim < n2.tim;
    }
    
    bool cmpx(node n1,node n2) {
        if (n1.x == n2.x)
            return n1.y < n2.y;
        else
            return n1.x < n2.x;
    }
    
    int lowbit(int x) {
        return x & (-x);
    }
    
    void change(int x,int dx) {
        while (x <= n) {
            c[x] += dx;
            x += lowbit(x);
        }
    }
    
    
    int getsum(int x) {
        int s = 0;
        while (x) {
            s += c[x];
            x -= lowbit(x);
        }
        return s;
    }
    
    void cdq(int l,int r) {
        if (l == r)
            return ;
        int mid = (l + r) / 2;
        cdq(l,mid);
        cdq(mid+1,r);
        sort(s+l,s+mid+1,cmpx);
        sort(s+mid+1,s+r+1,cmpx);
        int i,j = l;
        for (i = mid + 1;i <= r;i++) {
            while (j <= mid && s[j].x <= s[i].x) {
                if (s[j].op == 0)
                    change(s[j].y,s[j].v);
                j++;
            }
            if (s[i].op == 1)
                s[i].v += getsum(s[i].y);
        }
        for (i = l;i < j;i++)
            if (s[i].op == 0)
                change(s[i].y,-s[i].v);
    }
    
    int main() {
        int opt;
        cnt = 0;
        cin >> opt >> n;
        n++;
        cin >> opt;
        while (opt != 3) {
            if (opt == 1) {
                ++cnt;
                cin >> s[cnt].x >> s[cnt].y >> s[cnt].v;
                s[cnt].x += 1;
                s[cnt].y += 1;
                s[cnt].tim = cnt;
                s[cnt].id = cnt;
                s[cnt].op = 0;
            }
            else {
                int x,y,xx,yy;
                cin >> x >> y >> xx >> yy;
                xx++;
                yy++;
                ++cnt;
                s[cnt] = {cnt,x,y,0,cnt,1};
                ++cnt;
                s[cnt] = {cnt,xx,yy,0,cnt,1};
                ++cnt;
                s[cnt] = {cnt,x,yy,0,cnt,1};
                ++cnt;
                s[cnt] = {cnt,xx,y,0,cnt,1};
            }
            cin >> opt;
        }
        cdq(1,cnt);
        sort(s+1,s+1+cnt,cmptim);
        for (int i = 1;i <= cnt;i++) {
            if (s[i].op == 1) {
                cout << s[i].v + s[i+1].v - s[i+2].v - s[i+3].v << endl;
                i += 3;
            }
        }
        return 0;
    }

    总结,CDQ分治解决的2个经典模型:

    1. 三位偏序,若某个问题能转化成三维偏序(例如二维LIS等),可以直接套CDQ分治;(要按照特定的顺序)
    2. 某个问题的动态版本,若其静态版本可以在O(len)时间内解决,可以套一个CDQ分治解决其动态版本,复杂度多付出一个logM (可以任意顺序)

    博客转载来源:https://www.luogu.com.cn/blog/BeWild/cdq-fen-zhi

  • 相关阅读:
    BZOJ 3132: 上帝造题的七分钟 树状数组+差分
    PAT Advanced 1006 Sign In and Sign Out (25 分)
    PAT Advanced 1011 World Cup Betting (20 分)
    PAT Basic 1032 挖掘机技术哪家强 (20 分)
    PAT Basic 1028 人口普查 (20 分)
    PAT Basic 1004 成绩排名 (20 分)
    大数据数据库HBase(二)——搭建与JavaAPI
    PAT Advanced 1009 Product of Polynomials (25 分)(vector删除元素用的是erase)
    PAT Advanced 1002 A+B for Polynomials (25 分)(隐藏条件,多项式的系数不能为0)
    PAT Basic 1041 考试座位号 (15 分)
  • 原文地址:https://www.cnblogs.com/-Ackerman/p/12762479.html
Copyright © 2011-2022 走看看