前言骚话

本人蒟蒻,一开始看到模板题就非常的懵逼,链接,学到后面就越来越清楚了。
吐槽,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. openstack api快速入门

    原文:http://my.oschina.net/guol/blog/105430 openstack官方有提供api供开发者使用,可以使用api做一些外围的小工具,用来简化对openstack的管理 ...

  2. Redis 3.0正式版发布,正式支持Redis集群

    Redis是一个开源.基于C语言.基于内存亦可持久化的高性能NoSQL数据库,同时,它还提供了多种语言的API.近日,Redis 3.0在经过6个RC版本后,其正式版终于发布了.Redis 3.0的最 ...

  3. Eclipse中使用Junit编写测试用例

    Eclipse自带Junit插件,不用安装就能在项目中编写测试用例,非常方便. 在项目中添加Junit库 在编写测试用例之前,需要先引入Junit.对项目根目录右键,选择Properties,Java ...

  4. Android 中如何获取 H5 保存在 LocalStorage 的数据

    主要分三步: 写个接口,接收 Js 回调 添加到 WebView 主动调用 Js 获取 比如我要获取保存在 LocalStorage 中的 userKey 字段: 1.写个接口,接收 Js 回调 pu ...

  5. 转:一个跨WINDOWS LINUX平台的线程类

     来源:http://blog.csdn.net/dengxu11/article/details/7232681 继Windows下实现一个CThread封装类之后,这里我再实现一个跨WINDOWS ...

  6. Python内置函数(30)——hex

    英文文档: hex(x) Convert an integer number to a lowercase hexadecimal string prefixed with “0x”, for exa ...

  7. DELL OME监控服务器安装配置

    介绍 OME软件配合DELL的SA可以对服务器硬件进行监控,并且如果服务器出问题会自动联系DELL报修,方便我们管理维护,具体安装要求就不多写了,我用的机器4核8G内存200G硬盘空间,创建在Hype ...

  8. 4.91Python数据类型之(6)元组

    前言 有时候,我们为了数值的安全性,不许用户修改数据,今天我们就来讲讲关于python不可变的数据类型--- 元组数据 目录 1.元组的基本定义 2.元组的基本操作 (一)元组的基本定义 1.元组的概 ...

  9. 微信编辑器 wxEditor 最牛逼的富文本编辑器

    时尚最牛逼的富文本编辑器 http://wxeditor.leipi.org/ http://www.wwei.cn/

  10. xm数据写入

    reshape有两个参数: 其中,参数:cn为新的通道数,如果cn = 0,表示通道数不会改变. 参数rows为新的行数,如果rows = 0,表示行数不会改变. 注意:新的行*列必须与原来的行*列相 ...