Loading

cdq 分治

概念

cdq 分治是一种分治思想,用于处理序列中的点对关系等。

cdq 分治的主要思路是:

对于区间 \([l, r]\) 内的点对,记 \(m = \lfloor \frac{l + r}{2} \rfloor\),考虑:

  1. \([l, m]\) 内的点对

  2. 横跨 \([l, m]\)\((r, m]\) 的点对

  3. \((r, m]\) 内的点对

其中 1.3. 可以递归处理,主要考虑的是维护2。通常情况下使用 树状数组/线段树 等数据结构维护。

例题

维护序列内点对个数

通常用于维护序列内满足特定条件的点对。

例:三维偏序。

P3810 【模板】三维偏序(陌上花开)

对于考虑维护区间 \([l, r]\) 内的点对。

考虑区间 \([l, r]\) 内的点对。

沿用二维偏序的思路,同时考虑第三维的维护。首先将所有元素按 \(a\) 升序排序。如果我们考虑第 2 类点对,发现在区间左右两半的元素互不影响。即我们可以对两边的元素进行任意排序,因此考虑对于两边的元素分别按照 \(b\) 升序排序。

由于区间两半各满足单调性,因此可以考虑对区间的两半分别维护一个指针。具体地,令指针从对应的起点开始向右扫描,记左半部分的指针为 \(j\),右半部分的指针为 \(i\)。当 \(b_j \leq b_i\) 时,由于初始时按 \(a\) 升序排序,所以 \(a_j \leq a_i\)。又有 \(\forall l \leq k \leq j, a_k \leq a_i, b_k \leq b_j \leq b_i\),因此问题转化为求 \(k \in [l, j]\)\(c_k \leq c_i\)\(k\) 的个数。

容易联想到二维偏序。于是维护一个值域树状数组,每次 \(j\) 向右扫时用当前值的个数更新树状数组中 \(c_j\) 处的下标。查询时直接查询 \([1, c_i]\) 的总和即为所求。

有的题目可能需要离散化。

bool cmp2(int x, int y)
{
    if (b[x] != b[y]) return b[x] < b[y];
    return c[x] < c[y];
}

void cdq(int l, int r)
{
    if (l == r) return;
    int mid = (l + r) >> 1;
    cdq(l, mid);
    cdq(mid + 1, r);
    sort(p + l, p + mid + 1, cmp2);
    sort(p + mid + 1, p + r + 1, cmp2);
    int i = mid + 1, j = l;
    while (i <= r)
    {
        while ((j <= mid) && (b[p[i]] >= b[p[j]])) update(c[p[j]], v[p[j]]), j++;
        cnt[p[i]] += query(c[p[i]]), i++;
    }
    for (i = l; i < j; i++) update(c[p[i]], -v[p[i]]);
}

\(T(n) = T(\lfloor \frac{n}{2} \rfloor) + T(\lceil \frac{n}{2} \rceil) + n \log n = O(n \log^2 n)\)

优化特殊 1D/1D 动态规划

优化形如 \(f[i] = \max_{j = 1}^{i - 1} (f[j] \cdot [a_j \leq a_i] \cdot [b_j \leq b_i])\) 的动态规划。

例:优化最长不升子序列。

P2487 [SDOI2011]拦截导弹

【题解】P2487 [SDOI2011]拦截导弹

动态转静态

将一些数据结构问题从动态修改转化成静态维护。

要求:

  1. 可以离线。

  2. 修改之间互相独立,即先前的修改不会对后面修改的贡献造成影响。

P4169 [Violet]天使玩偶/SJY摆棋子

对于询问的点 \((x_i, y_i)\),不妨先只考虑在其左下方的点 \((x_j, y_j)\),然后通过旋转平面直角坐标系求出左下、右上、右下的贡献。

容易发现此时点 \(j\) 到点 \(i\) 的距离为 \((x_i + y_i) - (x_j + y_j)\),即我们要维护修改的点中最大的横纵坐标之和。

容易发现修改之间相互独立,并且修改对于询问的贡献次数是 \(O(n^2)\) 的,因此可以考虑用 cdq 分治优化。

具体地,对于第 \(l\) 次操作到第 \(r\) 次操作(含修改和询问),考虑:

  1. \([l, m]\) 中的修改 -> \([l, m]\) 中的询问。

  2. \([l, m]\) 中的修改 -> \((r, m]\) 中的询问。

  3. \((r, m]\) 中的修改 -> \((r, m]\) 中的询问。

对于 2,容易发现此时维护的是满足 \(j \in [l, m], i \in (m, r]\)\(j < i, x_j \leq x_i, y_j \leq y_i\) 的点对关系,于是考虑用三维偏序的方法处理。

时间复杂度 \(O(n \log^2 n)\)

void cdq(int l, int r)
{
    if (l == r) return;
    int mid = (l + r) >> 1;
    cdq(l, mid);
    cdq(mid + 1, r);
    sort(q + l, q + mid + 1);
    sort(q + mid + 1, q + r + 1);
    int i = mid + 1, j = l;
    while (i <= r)
    {
        while ((j <= mid) && (q[j].x <= q[i].x))
        {
            if (q[j].opt == 1) update(q[j].y, q[j].x + q[j].y);
            j++;
        }
        if (q[i].opt == 2)
        {
            int v = query(q[i].y);
            if (v) ans[q[i].idx] = min(ans[q[i].idx], q[i].x + q[i].y - v);
        }
        i++;
    }
    for (i = l; i <= mid; i++)
        if (q[i].opt == 1) clear(q[i].y);
}

四维偏序

cdq 套 cdq,理论上可以一直套下去,五维偏序或者以上也是可以做的,每套一层 cdq 就会多 \(O(\log n)\) 的复杂度。

例题:【HDU5126 Stars】

考虑套两层 cdq.

这里钦定每个元素的第一维是时间轴,后三维对应点的坐标。

  • 将所有元素按照第一维排序。

这里因为第一维是时间轴不用排序。

  • 第一层 cdq.

    • 考虑先分治左右两半区间,使得子区间的第二维坐标有序。

    • 考虑对于右半区间的每一个询问,处理出左半区间中能影响到它的询问,按序 排好。

    • 考虑对排好的操作进行第二层 cdq 分治,无有效操作则不操作。

    • 按第二维坐标归并合并当前区间。

  • 第二层 cdq.

    • 考虑分治左右两半区间,使得子区间的第三维坐标有序。

    • 考虑对于右半区间的每一个询问,用树状数组维护左半区间中可以影响到它的修改的贡献(维护第四维坐标)。

    • 使用树状数组维护询问答案。

    • 撤销左半区间在树状数组中的影响。

    • 按第三维坐标归并合并当前区间。

另外有一种写法是考虑到:只有两次都被划分到左半区间的修改 可以影响 两次都被划分到右半区间的修改。于是考虑对于元素进行标记,然后在第二层 cdq 中相应处理。

这种写法常数极大,可以简单卡掉,但时间复杂度也是 \(T(n) = O(n \log n) + 2 T(\frac{n}{2}) = O(n \log n)\).

时间复杂度是 \(T(n) = O(n) + 2 T(\frac{n}{2}) = O(n \log^2 n)\).

#include <cstdio>
#include <algorithm>
using namespace std;

#define il inline

const int maxq = 5e4 + 5;
const int opt_sz = 5e5 + 5;

struct Query
{
    int x, y, z, t, coe, idx;
} nd[opt_sz], s1[opt_sz], s2[opt_sz];

int q;
int ans[maxq];
int tmp[opt_sz];

namespace BIT
{
    int c[opt_sz];

    il int lowbit(int x) { return x & (-x); }

    il void update(int p, int w) { for (int i = p; i <= (q << 3); i += lowbit(i)) c[i] += w; }

    il int query(int p) { int res = 0; for (int i = p; i; i -= lowbit(i)) res += c[i]; return res; }
}

il int read()
{
    int res = 0;
    char ch = getchar();
    while ((ch < '0') || (ch > '9')) ch = getchar();
    while ((ch >= '0') && (ch <= '9')) res = res * 10 + ch - '0', ch = getchar();
    return res;
}

il void write(int x)
{
    if (x > 9) write(x / 10);
    putchar(x % 10 + '0');
}

il bool cmp_x(const Query& a, const Query& b)
{
    if (a.x != b.x) return (a.x < b.x);
    if (a.y != b.y) return (a.y < b.y);
    if (a.z != b.z) return (a.z < b.z);
    return (a.t < b.t);
}

il bool cmp_y(const Query& a, const Query& b)
{
    if (a.y != b.y) return (a.y < b.y);
    if (a.z != b.z) return (a.z < b.z);
    if (a.t != b.t) return (a.t < b.t);
    return (a.x < b.x);
}

il bool cmp_z(const Query& a, const Query& b)
{
    if (a.z != b.z) return (a.z < b.z);
    if (a.t != b.t) return (a.t < b.t);
    if (a.x != b.x) return (a.x < b.x);
    return (a.y < b.y);
}

il void cdq2(int l, int r)
{
    if (l >= r) return;
    int mid = (l + r) >> 1;
    cdq2(l, mid), cdq2(mid + 1, r);
    int i = mid + 1, j = l;
    for ( ; i <= r; i++)
    {
        if (!s1[i].coe) continue;
        for ( ; (j <= mid) && (s1[j].y <= s1[i].y); j++)
            if (!s1[j].coe) BIT::update(s1[j].z, 1);
        ans[s1[i].idx] += s1[i].coe * BIT::query(s1[i].z);
    }
    for (i = l; i < j; i++)
        if (!s1[i].coe) BIT::update(s1[i].z, -1);
    int k = mid + 1; j = l;
    for (i = l; i <= r; i++)
        if ((k > r) || ((j <= mid) && (s1[j].y <= s1[k].y))) s2[i] = s1[j++];
        else s2[i] = s1[k++];
    for (i = l; i <= r; i++) s1[i] = s2[i];
}

il void cdq1(int l, int r)
{
    if (l >= r) return;
    int mid = (l + r) >> 1, len = 0;
    cdq1(l, mid), cdq1(mid + 1, r);
    int i = mid + 1, j = l;
    for ( ; i <= r; i++)
    {
        if (!nd[i].coe) continue;
        for ( ; (nd[j].x <= nd[i].x) && (j <= mid); j++)
            if (!nd[j].coe) s1[++len] = nd[j];
        s1[++len] = nd[i];
    }
    if (len) cdq2(1, len);
    int k = mid + 1; j = l;
    for (int i = l; i <= r; i++)
        if ((k > r) || ((j <= mid) && nd[j].x <= nd[k].x)) s1[i] = nd[j++];
        else s1[i] = nd[k++];
    for (int i = l; i <= r; i++) nd[i] = s1[i];
}

int main()
{
    q = read();
    int len = 0, cnt = 0;
    for (int i = 1, opt, x, y, z, X, Y, Z; i <= q; i++)
    {
        opt = read();
        if (opt == 1)
        {
            ans[i] = -1;
            cnt++, x = read(), y = read(), tmp[++cnt] = z = read();
            len++, nd[len] = (Query){x, y, z, len, 0, i};
        }
        else
        {
            cnt++, x = read() - 1, y = read() - 1, tmp[++cnt] = z = read() - 1;
            cnt++, X = read(), Y = read(), tmp[++cnt] = Z = read();
            len++, nd[len] = (Query){x, y, z, len, -1, i};
            len++, nd[len] = (Query){X, y, z, len, 1, i};
            len++, nd[len] = (Query){x, Y, z, len, 1, i};
            len++, nd[len] = (Query){x, y, Z, len, 1, i};
            len++, nd[len] = (Query){x, Y, Z, len, -1, i};
            len++, nd[len] = (Query){X, y, Z, len, -1, i};
            len++, nd[len] = (Query){X, Y, z, len, -1, i};
            len++, nd[len] = (Query){X, Y, Z, len, 1, i};
        }
    }
    sort(tmp + 1, tmp + cnt + 1);
    int cur = unique(tmp + 1, tmp + cnt + 1) - tmp - 1;
    for (int i = 1; i <= len; i++) nd[i].z = lower_bound(tmp + 1, tmp + cur + 1, nd[i].z) - tmp;
    cdq1(1, len);
    for (int i = 1; i <= q; i++)
        if (ans[i] != -1) write(ans[i]), putchar('\n');
    return 0;
}
posted @ 2022-07-11 21:10  kymru  阅读(220)  评论(0编辑  收藏  举报