初学cdq分治学习笔记(可能有第二次的学习笔记)
前言骚话
本人蒟蒻,一开始看到模板题就非常的懵逼,链接,学到后面就越来越清楚了。
吐槽,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分治学习笔记(可能有第二次的学习笔记)的更多相关文章
- 初学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 ...
- 【学习笔记】CDQ分治(等待填坑)
因为我对CDQ分治理解不深,所以这篇博客只是我现在的浅显理解有任何不对的,希望大佬指出. 首先就是CDQ分治适用的题型: (1)带修改,但修改互相独立 (2)必须允许离线 (3)解决数据结构的题,能把 ...
- ACdream1157 Segments(CDQ分治 + 线段树)
题目这么说的: 进行如下3种类型操作:1)D L R(1 <= L <= R <= 1000000000) 增加一条线段[L,R]2)C i (1-base) 删除第i条增加的线段, ...
- 学习笔记 | CDQ分治
目录 前言 啥是CDQ啊(它的基本思想) 例题 后记 参考博文 前言 博主太菜了 学习快一年的OI了 好像没有什么会的算法 更寒碜的是 学一样还不精一样TAT 如有什么错误请各位路过的大佬指出啊感谢! ...
- 一篇自己都看不懂的CDQ分治&整体二分学习笔记
作为一个永不咕咕咕的博主,我来更笔记辣qaq CDQ分治 CDQ分治的思想还是比较简单的.它的基本流程是: \(1.\)将所有修改操作和查询操作按照时间顺序并在一起,形成一段序列.显然,会影响查询操作 ...
- [学习笔记] CDQ分治 从感性理解到彻底晕菜
最近学了一种叫做CDQ分治的东西...用于离线处理一系列操作与查询似乎跑得很快233 CDQ的名称似乎源于金牌选手陈丹琦 概述: 对于一坨操作和询问,分成两半,单独处理左半边和处理左半边对于右半边的影 ...
- [偏序关系与CDQ分治]【学习笔记】
组合数学真是太棒了 $CDQ$真是太棒了(雾 参考资料: 1.<组合数学> 2.论文 课件 很容易查到 3.sro __stdcall 偏序关系 关系: 集合$X$上的关系是$X$与$X$ ...
- CDQ分治与整体二分学习笔记
CDQ分治部分 CDQ分治是用分治的方法解决一系列类似偏序问题的分治方法,一般可以用KD-tree.树套树或权值线段树代替. 三维偏序,是一种类似LIS的东西,但是LIS的关键字只有两个,数组下标和 ...
- CDQ分治学习笔记
数据结构中的一块内容:$CDQ$分治算法. $CDQ$显然是一个人的名字,陈丹琪(NOI2008金牌女选手) 这种离线分治算法被算法界称为"cdq分治" 我们知道,一个动态的问题一 ...
随机推荐
- OpenCL:图像处理基础note
使用图像对象的理由 虽然对于图像也可以把它的像素数据当做一般的缓存数据来处理,但是如果把它当做图像来处理有如下好处: 在GPU中,图像数据是保存在特殊的全局内存中,即纹理内存,它和一般的全局内存不相同 ...
- 推荐一款MongoDB的客户端管理工具--nosqlbooster
今天给大家推荐一款MongoDB的客户端工具--nosqlbooster,这个也是我工作中一直使用的连接管理MongoDB的工具.这个工具还有个曾用名--mongobooster.nosqlboost ...
- 【原】Java学习笔记024 - 包装类
package cn.temptation; public class Sample01 { public static void main(String[] args) { // 之前对于基本数据类 ...
- 安装ESXi部署OVF详细步骤
整个安装部署过程均在个人环境进行.欧克,我们现在开始. 一.安装ESXi 1.Enter回车 2.Enter回车继续 3.F11,接受继续 4.Enter,回车继续(选择安装ESXi的设备) 5.默认 ...
- LeetCode算法题-Longest Harmonious Subsequence(Java实现)
这是悦乐书的第270次更新,第284篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第136题(顺位题号是594).我们定义一个和谐数组是一个数组,其最大值和最小值之间的差 ...
- 《常见排序算法--PHP实现》
原文地址: 本文地址:http://www.cnblogs.com/aiweixiao/p/8202360.html Original 2018-01-02 关注 微信公众号 程序员的文娱情怀 1.概 ...
- OpenResty:通过 Lua 扩展 NGINX 实现的可伸缩的 Web 平台
关于 http://openresty.org/cn/about.html 这个开源 Web 平台主要由章亦春(agentzh)维护.在 2011 年之前曾由淘宝网赞助,在后来的 2012 ~ 201 ...
- Zabbix 3.4.7针对一些主机设置期间维护
场景说明: 由于公司有些主机设置了定时开机关机,每次开机关机得时候都会发邮件告警,每次都需要值班人员提醒,为了处理这种无效告警,可以在zabbix中设置维护 zabbix中的维护---维护期间:用来设 ...
- Oracle 执行计划(三)-------表连接方式
SQL FOR TESTING: create table qcb_student_test( student_id number, student_name varchar2(20), studen ...
- 哈希长度扩展攻击(Hash Length Extension Attack)利用工具hexpand安装使用方法
去年我写了一篇哈希长度扩展攻击的简介以及HashPump安装使用方法,本来已经足够了,但HashPump还不是很完善的哈希长度扩展攻击,HashPump在使用的时候必须提供original_data, ...