ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

【Coel.学习笔记】【半途跑路】CDQ 分治

2022-09-08 20:32:35  阅读:157  来源: 互联网

标签:ch 半途 分治 Coel leq int CDQ 排序


最近在刷状压 DP,结果发现太难不会做,跑来学点别的。
反正 CSP-S2 之前刷完就行了,吧?
放在数据结构里面是因为 CDQ 分治和数套树能解决的问题差不多,所以放了进去(绝不是因为懒得开一个“离线算法”的 Tag!)

引入

CDQ 分治是一种通过把动态询问/点对问题等离线处理,并分治求解的算法。这种思想最早于 IOI2008 国家集训队选手陈丹琦在其论文 从《Cash》谈一类分治算法的应用 中总结,故得名。

例题讲解

虽然原论文给出的例题是[NOI2007] 货币兑换,但我们先从另一个经典题目——三维偏序讲起。

【模板】三维偏序

洛谷传送门
有 $ n $ 个元素,第 $ i $ 个元素有 $ a_i,b_i,c_i $ 三个属性,设 $ f(i) $ 表示满足 $ a_j \leq a_i $ 且 $ b_j \leq b_i $ 且 $ c_j \leq c_i $ 且 $ j \ne i $ 的 \(j\) 的数量。

对于 $ d \in [0, n-1] $,求 $ f(i) = d $ 的数量。(这里描述用 \(n-1\) 代替右开)

解析:这题有很多做法,如树套树、K-D Tree。这里当然只介绍 CDQ 分治的做法。
假设只有一个属性,即对每个 \(i\) 找到 \(a_j\leq a_i\) 的数量。显然我们只要按照 \(a_i\) 大小排序,那么对于 \(a_i\) 就有 \(i-1\) 个数字比它小。
当属性有两个的时候,同样按照 \(a_i\) 排序,然后从前往后扫描。那么对于 \(i\) 前面的元素 \(j\) 一定有 \(a_j\leq a_i\),任务就变成找 \(b_j\leq b_i\) 的数量。一种办法是先把 \(b\) 离散化,然后用类似树状数组求逆序对的方式求解即可。


当然也可以像归并排序求逆序对一样,先做排序,然后根据 \(i,j\) 在两个分治区间内的情况分类讨论:

  1. 若 \(i,j\) 均在左区间,则对左区间分治求解;
  2. 若 \(i,j\) 均在右区间,则对右区间分治求解;
  3. 若 \(i,j\) 分别在左、右区间(显然 \(i\) 一定在 \(j\) 右边),那么对于左区间的任何一个元素,寻找 \(b_j\leq b_i\) 的数量即可。由于分治同时会把 \(b\) 排序,所以对 \(i,j\) 各开一个指针,双指针扫描一遍就可以求出答案。

这样每次分治都要做一次 \(O(n)\) 的双指针,总复杂度为 \(O(n\log n)\)。


现在来到三维。类比上面提到的归并排序方法,先做一遍排序。那么对于每个 \(i\),满足条件的 \(j\) 一定在左边。同样,我们对 \(i,j\) 的情况做分类讨论:同在左区间,同在右区间,分别位于两个区间。前两个分治即可,重点看第三个。

由于做了排序,所以 \(a_j\leq a_i\) 一定满足。 接下来对于 \(b_j\leq b_i\),用双指针寻找第一个 \(b_j> b_i\) 的位置,那么每个 \(i\) 都可以用 \(O(n)\) 找到对应的 \(j\),即答案在左区间边界到 \(j-1\) 的范围内。最后对于 \(c_j\leq c_i\),用树状数组就可求出。

这样每次分治都要做 \(O(n)\) 双指针,且每次移动指针都要用 \(O(\log n)\) 的树状数组维护答案,故总时间复杂度为 \(O(n\log ^2 n)\)。

总结一下,CDQ 分治的本质其实就是消除偏序维度。对于一维,直接排序;对于二维。在一维的基础上做树状数组/分治;对于三维,再在二维基础上加。从某种程度上说,CDQ 分治也可以看作归并排序一类分治的扩展算法。顺带一提,树套树求三维偏序的本质也是消除维度,不过是用权值线段树代替分治、支持强制在线罢了。

#include<cctype>
#include<cstdio>
#include<algorithm>
#include<cstring>

struct __Coel_FastIO {
#ifndef LOCAL
#define _getchar_nolock getchar_unlocked
#define _putchar_nolock putchar_unlocked
#endif

    inline __Coel_FastIO& operator>>(int& x) {
        x = 0;
        bool f = false;
        char ch = _getchar_nolock();
        while (!isdigit(ch)) {
            if (ch == '-') f = true;
            ch = _getchar_nolock();
        }
        while (isdigit(ch)) {
            x = x * 10 + ch - '0';
            ch = _getchar_nolock();
        }
        if (f) x = -x;
        return *this;
    }

    inline __Coel_FastIO& operator<<(int x) {
        if (x < 0) {
            x = -x;
            _putchar_nolock('-');
        }
        static int buf[35];
        int top = 0;
        do {
            buf[top++] = x % 10;
            x /= 10;
        } while (x);
        while (top) _putchar_nolock(buf[--top] + '0');
        return *this;
    }

    inline __Coel_FastIO& operator<<(char x) {
        return _putchar_nolock(x), *this;
    }

} qwq;

const int maxn = 2e5 + 10;

int n, m, top = 1;
int ans[maxn];

struct node {
    int a, b, c;
    int res, cnt; //记录出现次数,处理属性相等的状态
    bool operator<(const node &x) const {
        if (a != x.a) return a < x.a;
        if (b != x.b) return b < x.b;
        return c < x.c;
    }
    bool operator==(const node &x) const {
        return a == x.a && b == x.b && c == x.c;
    }
} q[maxn], tem[maxn];

class Fenwick_Tree {
    private:
#define lowbit(x) (x & (-x))
        int c[maxn];
    public:
        void add(int x, int v) {
            for (int i = x; i < maxn; i += lowbit(i)) c[i] += v;
        }
        int query(int x) {
            int res = 0;
            for (int i = x; i; i -= lowbit(i)) res += c[i];
            return res;
        }
} T;

void CDQ_Divide(int l, int r) {
    if (l >= r) return;
    int mid = (l + r) >> 1;
    CDQ_Divide(l, mid), CDQ_Divide(mid + 1, r);
    int i = l, j = mid + 1, k = 0;
    while (i <= mid && j <= r) //两个区间都没遍历完时
        if (q[i].b <= q[j].b) //不满足条件,把信息存入树状数组
            T.add(q[i].c, q[i].cnt), tem[k++] = q[i++];
        else // 满足条件,在树状数组上得到答案
            q[j].res += T.query(q[j].c), tem[k++] = q[j++];
    while (i <= mid) T.add(q[i].c, q[i].cnt), tem[k++] = q[i++];
    while (j <= r) q[j].res += T.query(q[j].c), tem[k++] = q[j++]; //将没有处理完的部分继续处理
    for (i = l; i <= mid; i++) T.add(q[i].c, -q[i].cnt); //还原树状数组
    for (i = l, j = 0; j < k; i++, j++) q[i] = tem[j]; //将归并排序后结果复制到原数据中
}

int main(void) {
    qwq >> n >> m;
    for (int i = 0; i < n; i++)
        qwq >> q[i].a >> q[i].b >> q[i].c, q[i].cnt = 1;
    std::sort(q, q + n);
    for (int i = 1; i < n; i++) { // 特判属性相等时的答案
        // 这里的 top 其实也起到了去重的作用
        if (q[i] == q[top - 1]) q[top - 1].cnt++;
        else q[top++] = q[i];
    }
    CDQ_Divide(0, top - 1);
    for (int i = 0; i < top; i++)
        ans[q[i].res + q[i].cnt - 1] += q[i].cnt;
    for (int i = 0; i < n; i++)
        qwq << ans[i] << '\n';
    return 0;
}

标签:ch,半途,分治,Coel,leq,int,CDQ,排序
来源: https://www.cnblogs.com/Coel-Flannette/p/16670766.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有