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

  • 相关阅读:
    Spring 核心API
    python装饰器
    python作业(day1)
    Kali Linux 更新源
    一维数组模拟数据结构-------栈
    Spring事务管理
    Linux用户管理命令
    Linux 帮助命令
    Spring对jdbc的支持
    springboot集成shiro 循环重定向
  • 原文地址:https://www.cnblogs.com/-Ackerman/p/12762479.html
Copyright © 2011-2022 走看看