前言骚话

本人蒟蒻,一开始看到模板题就非常的懵逼,链接,学到后面就越来越清楚了。
吐槽,cdq,超短裙分治。。。。(尴尬)

正片开始

思想

和普通的分治,还是分而治之,但是有一点不一样的是一般的分治在合并问题答案是,左右区间是分开来的,也就是左区间的答案不会对右区间的答案造成贡献,但是cdq分治要处理的就是左区间对于右区间的答案。
很多情况下,cdq分治都可以解决掉一维的答案,简单的来说就是直接去掉一个嵌套的数据结构,简直将代码量降至低谷,但是有一个很明显的缺点就是只能实现离线操作。QwQ


还是和题目一起将比较好,我们从\(2\)维偏序一直讲到n维偏序吧,(滑稽)
那么考虑偏序性的问题,最重要的是要保证答案的正确性,因为当前的处理不能影响到后面的状态。
二维偏序:第一维:排序解决,第二维:归并排序cdq或者是树状数组都可以,虽然有一点超出了cdq的范围,但是还是可以用cdq来实现的。
三维偏序:第一维:排序解决,第二维:cdq分治,第三维:树状数组。还有一种写法就是后两维用树套树来做。这个时候就非常明显可以体现出cdq分治的优越性了。树套树:代码直奔100行,cdq一般是不会超过70行。


详细讲一下:https://www.cnblogs.com/chhokmah/p/10571403.html,注意这里讲的不怎么详细
首先将第一维排序,因为不是逆序对,那么就不需要维护编号,然后将所有一样的数都合并起来,因为我们也是要统计所有相同的个数。那么我们开始第二维的操作,因为有了第一维的顺序的限制,那么我们就不能随意的查找,这时候我们就是要开始求逆序对了。还是将现在区间进行排序,从小到大,这样我们分治,在将所有右区间内遍历一遍,如果右区间中有y小于左区间内的数,那么就在当前这个z上加上个数,表示这一段都是由答案的贡献,然后将答案统计在新的数组中就可以了。

#include <bits/stdc++.h>
#define ll long long
#define ms(a, b) memset(a, b, sizeof(a))
#define inf 0x3f3f3f3f
using namespace std;
template <typename T>
inline void read(T &x) {
    x = 0; T fl = 1;
    char ch = 0;
    while (ch < '0' || ch > '9') {
        if (ch == '-') fl = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    x *= fl;
}
#define N 100005
struct data {
    int x, y, z, res, cnt;
    data() {
        x = y = z = res = cnt = 0;
    }
}a[N], v[N];
int n, k;
int ans[N];
struct BIT{
    #define lowbit(x) (x&-x)
    int n, tr[N];
    void add(int x, int val) {
        for (; x <= n; x += lowbit(x)) tr[x] += val;
    }
    int query(int x) {
        int res = 0;
        for (; x; x -= lowbit(x)) res += tr[x];
        return res;
    }
}tr;
bool cmp1(const data &a, const data &b) {
    if (a.x == b.x)
        if (a.y == b.y) return a.z < b.z;
        else return a.y < b.y;
    else return a.x < b.x;
}
bool cmp2(const data &a, const data &b) {
    if (a.y == b.y) return a.z < b.z;
    else return a.y < b.y;
}
void cdq(int l, int r) {
    if (l == r) return;
    int mid = (l + r) >> 1;
    cdq(l, mid);
    cdq(mid + 1, r);
    sort(v + l, v + mid + 1, cmp2);
    sort(v + mid + 1, v + r + 1, cmp2);
    int l1 = l, l2 = mid + 1;
    while (l2 <= r) {
        while (l1 <= mid && v[l1].y <= v[l2].y)
            tr.add(v[l1].z, v[l1].cnt), l1 ++;
        v[l2].res += tr.query(v[l2].z);
        l2 ++;
    }
    for (int i = l; i < l1; i ++) tr.add(v[i].z, -v[i].cnt);
}
int main() {
    read(n); read(k);
    tr.n = k;
    for (int i = 1; i <= n; i ++) {
        read(a[i].x); read(a[i].y); read(a[i].z);
    }
    sort(a + 1, a + 1 + n, cmp1);
    int tot = 0;
    for (int i = 1, j = 1; i <= n; i = j) {
        v[++ tot] = a[i];
        while (a[i].x == a[j].x && a[i].y == a[j].y && a[i].z == a[j].z && j <= n)
            j ++, v[tot].cnt ++;
    }
    cdq(1, tot);
    for (int i = 1; i <= tot; i ++)
        ans[v[i].res + v[i].cnt] += v[i].cnt;
    for (int i = 1; i <= n; i ++) printf("%d\n", ans[i]);
    return 0;
}

关于四位偏序:第一维:排序,第二维:cdq分治,第三维:套一个cdq分治,第四位,树状数组。
和上面的思路差不多,只是代码更加冗长,但是想到树套树套树可能要飙到200+就感觉到非常欣慰。
没有时间写,就不写了。


关于五维偏序:我选择\(O(n^2)\)。这样可能更快。如果是用cdq来做的话,复杂度目测是\({log_2^n}^4\),而且代码复杂度为cdq套cdq套树套树,想想就可怕。


对于cdq分治的注意事项:千万不要用复杂度大的c++自带函数,比如说是memset,这样你会找都找不到自己错在哪里。
为了简化时间复杂度与常数,有几种方法可以参考:
(1)在分治之前先按照某一关键字排序,之后在分治过程中,将信息按照时间分成前后两部分,这样避免了多次排序。
(2)在分治过程中,利用归并排序的方式将两个有序序列合并,将O(nlog)的排序变为O(n)的归并。
(3)在分治过程中,利用树状数组解决问题,除非必须用到别的东西。
(4)在分治过程中,利用有序的性质可以发现,逆序也是有序的,并且满足一些正好与正序相反,这样可以避免重复排序。
(5)在分治之前尽可能的简化不必要的信息,这样能减少整个代码的常数。
(6)另外,在更新右区间或者合并的时候,尽量选择常数与时间复杂度较小的算法,比如说能用单调队列就不要用斜率优化,能用斜率优化就不要用决策单调性。


在举一个例子:动态逆序对
题解咕咕一会:【传送门】

#include <bits/stdc++.h>
#define ll long long
#define N 100005
using namespace std;
struct BIT{
    #define lowbit(x) (x&-x)
    int n, tr[N];
    void add(int x, int val) {
        for (; x <= n; x += lowbit(x)) tr[x] += val;
    }
    int query(int x) {
        int res = 0;
        for (; x; x -= lowbit(x)) res += tr[x];
        return res;
    }
}tr;
struct Que {
    int cnt, v, d, id, t;
}q[N << 1];
int n, m;
ll ans[N];
int a[N], pos[N];
bool cmp(const Que &a, const Que &b) {
    return a.d < b.d;
}
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, cmp);
    sort(q + mid + 1, q + 1 + r, cmp);
    int l1 = l, l2 = mid + 1;
    while (l2 <= r) {
        while (l1 <= mid && q[l1].d <= q[l2].d) tr.add(q[l1].v, q[l1].cnt), ++ l1;
        ans[q[l2].id] += q[l2].cnt * (tr.query(n) - tr.query(q[l2].v));
        l2 ++;
    }
    for (int i = l; i < l1; i ++) tr.add(q[i].v, -q[i].cnt);
    l1 = r; l2 = mid;
    while (l1 > mid) {
        while (l2 >= l && q[l2].d >= q[l1].d) tr.add(q[l2].v, q[l2].cnt), -- l2;
        ans[q[l1].id] += q[l1].cnt * tr.query(q[l1].v - 1);
        l1 --;
    }
    for (int i = mid; i > l2; i --) tr.add(q[i].v, -q[i].cnt);
}
int main() {
    scanf("%d%d", &n, &m);
    int tot = 0;
    tr.n = n;
    for (int i = 1; i <= n; i ++) {
        scanf("%d", &a[i]);
        pos[a[i]] = i;
        q[++ tot] = (Que){1, a[i], i, 0, tot};
    }
    for (int i = 1; i <= m; i ++) {
        int x; scanf("%d", &x);
        q[++ tot] = (Que){-1, x, pos[x], i, tot};
    }
    cdq(1, tot);
    for (int i = 1; i <= m; i ++) ans[i] += ans[i - 1];
    for (int i = 0; i < m; i ++) printf("%lld\n", ans[i]);
    return 0;
}

初学cdq分治学习笔记(可能有第二次的学习笔记)的更多相关文章

  1. 初学CDQ分治-NEU1702

    关于CDQ分治,首先需要明白分治的复杂度. T(n) = 2T(n/2)+O(kn), T(n) = O(knlogn) T(n) = 2T(n/2)+O(knlogn), T(n) = O(knlo ...

  2. ACdream1157 Segments(CDQ分治 + 线段树)

    题目这么说的: 进行如下3种类型操作:1)D L R(1 <= L <= R <= 1000000000) 增加一条线段[L,R]2)C i (1-base) 删除第i条增加的线段, ...

  3. 学习笔记 | CDQ分治

    目录 前言 啥是CDQ啊(它的基本思想) 例题 后记 参考博文 前言 博主太菜了 学习快一年的OI了 好像没有什么会的算法 更寒碜的是 学一样还不精一样TAT 如有什么错误请各位路过的大佬指出啊感谢! ...

  4. 一篇自己都看不懂的CDQ分治&amp;整体二分学习笔记

    作为一个永不咕咕咕的博主,我来更笔记辣qaq CDQ分治 CDQ分治的思想还是比较简单的.它的基本流程是: \(1.\)将所有修改操作和查询操作按照时间顺序并在一起,形成一段序列.显然,会影响查询操作 ...

  5. [学习笔记] CDQ分治 从感性理解到彻底晕菜

    最近学了一种叫做CDQ分治的东西...用于离线处理一系列操作与查询似乎跑得很快233 CDQ的名称似乎源于金牌选手陈丹琦 概述: 对于一坨操作和询问,分成两半,单独处理左半边和处理左半边对于右半边的影 ...

  6. [偏序关系与CDQ分治]【学习笔记】

    组合数学真是太棒了 $CDQ$真是太棒了(雾 参考资料: 1.<组合数学> 2.论文 课件 很容易查到 3.sro __stdcall 偏序关系 关系: 集合$X$上的关系是$X$与$X$ ...

  7. CDQ分治与整体二分学习笔记

     CDQ分治部分 CDQ分治是用分治的方法解决一系列类似偏序问题的分治方法,一般可以用KD-tree.树套树或权值线段树代替. 三维偏序,是一种类似LIS的东西,但是LIS的关键字只有两个,数组下标和 ...

  8. CDQ分治学习笔记

    数据结构中的一块内容:$CDQ$分治算法. $CDQ$显然是一个人的名字,陈丹琪(NOI2008金牌女选手) 这种离线分治算法被算法界称为"cdq分治" 我们知道,一个动态的问题一 ...

  9. [学习笔记]CDQ分治和整体二分

    序言 \(CDQ\) 分治和整体二分都是基于分治的思想,把复杂的问题拆分成许多可以简单求的解子问题.但是这两种算法必须离线处理,不能解决一些强制在线的题目.不过如果题目允许离线的话,这两种算法能把在线 ...

随机推荐

  1. .NET 对接JAVA 使用Modulus,Exponent RSA 加密

    最近有一个工作是需要把数据用RSA发送给Java 虽然一开始标准公钥 net和Java  RSA填充的一些算法不一样 但是后来这个坑也补的差不多了 具体可以参考 http://www.cnblogs. ...

  2. WebGIS中基于AGS的画圆查询简析以及通过Polygon来构造圆的算法

    文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/. 1.背景 某个项目需求中需要在前端进行画圆查询,将圆范围上的多边形要素 ...

  3. EarthWarrior3D游戏ios源码

    这是一款不错的ios源码源码,EarthWarrior3D游戏源码, 并且游戏源代码支持多平台. 适用于cocos v2.1.0.0版本 源码下载:http://code.662p.com/view/ ...

  4. 数理方程:Laplace变换 &amp; 留数(更新中)

    更新:25 APR 2016 Laplace变换 设函数\(f(t)\)在\(t>0\)时有定义,积分 \(F(s)=\int_0^{+\infty}f(t)e^{-st}dt \qquad ( ...

  5. JS传参出现乱码(转载)

    问题说明:在进行网站开发时,将表单的提交功能交给JS来传递,但是在传递中文的过程中出现类似于繁体字的乱码. 解决方案:为了解决这个问题,首先从底层的C#代码审查,重新设置页面传值进行模拟,但是几经测试 ...

  6. android setCompoundDrawables和setCompoundDrawablesWithIntrinsicBounds区别

    手工设置文本与图片相对位置时,常用到如下方法: setCompoundDrawables(left, top, right, bottom) setCompoundDrawablesWithIntri ...

  7. c/c++ 二叉排序树

    c/c++ 二叉排序树 概念: 左树的所有节点的值(包括子节点)必须小于中心节点,右树所有节点的值(包括子节点)必须大于中心节点. 不允许有值相同的节点. 二叉排序树的特点: 中序遍历后,就是从小到大 ...

  8. windows下navicate for mysql 零填充不显示

    在mysql数据库中我们在需要某个字段时经常要用到零填充 zerofill,之前碰到了一个大坑,在数据表sql语句中明明规定了 unsigned zerofill:但是一直没有显示出来,以为自己写的s ...

  9. [转帖]紫光展锐5G芯片

    紫光展锐5G芯片已流片:7nm工艺 2019年问世   https://news.mydrivers.com/1/612/612346.htm 本文转载自超能网,其他媒体转载需经超能网同意 高通骁龙X ...

  10. 苏州Uber优步司机奖励政策(4月23日)

    滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...