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

原文地址:https://www.cnblogs.com/-Ackerman/p/12762479.html