「学习笔记」CDQ分治
CDQ 分治的思想最早由 IOI2008 金牌得主陈丹琦在高中时整理并总结,目前这个思想的拓展十分广泛。
- 优点:可以将数据结构或者 DP 优化掉一维
- 缺点:这是离线算法。
引入
让我们来看一个问题
有 $ 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) $,求 $ f(i) = d $ 的数量。
$ 1 \leq n \leq 10^5$,$1 \leq a_i, b_i, c_i \le k \leq 2 \times 10^5 $。
这是一个三维偏序问题。
偏序问题:给定序列 \(A\),其中有序对 \((A_i, A_j)\),满足 \(i < j\) 且 \(A_i < A_j\) 这样的有序对我们称之为逆序对, 信息学竞赛中的逆序对问题,一般是要我们计数给出序列的逆序对个数的总和。其实可以把它看成一个特殊的二维偏序问题,或者说是离散化 \(x\) 坐标的二维偏序问题。
而 CDQ 分治,可以来解决三维偏序问题。
上面的引入问题就是模板题 P3810 【模板】三维偏序(陌上花开) 的题意。
P3810 【模板】三维偏序(陌上花开)
变量及其含义
struct node {
int x, y, z, cnt, ans;
} s1[N], s2[N];
x, y, z: 三个元素。
cnt:相同元素的个数。
ans:统计答案。
对于第一维 \(a\),我们可以先从小到大 sort 一遍,\(i\) 号点前面的点的 \(a\) 都比 \(a_i\) 小,这样我们就减少了一维的处理,还剩下两维。
bool cmp1(node a, node b) {
if (a.x == b.x) {
if (a.y == b.y) {
return a.z < b.z;
}
else return a.y < b.y;
}
return a.x < b.x;
}
// main() 函数里面
n = read<int>(), k = read<int>();
mx = k;
for (int i = 1, x, y, z; i <= n; ++ i) {
x = read<int>(), y = read<int>(), z = read<int>();
s1[i].x = x, s1[i].y = y, s1[i].z = z;
}
sort(s1 + 1, s1 + n + 1, cmp1);
排完序后,我们可以将相同的元素合并为一个元素,结构体里的 cnt 就派上用场了。
int top = 0;
for (int i = 1; i <= n; ++ i) {
++ top;
if (s1[i].x != s1[i + 1].x || s1[i].y != s1[i + 1].y || s1[i].z != s1[i + 1].z) {
s2[++ m].x = s1[i].x;
s2[m].y = s1[i].y;
s2[m].z = s1[i].z;
s2[m].cnt = top;
top = 0;
}
}
然后处理第二维,对于第二维,我们要求 \(b_j \leq b_i\),按照前面的思路,我们肯定也要想方设法给第二维排序。
我们可以用 归并排序 的思想,先分别给左半个区间和右半个区间按照第二维从小到大排序,然后依次处理,由于是在 \(a\) 排好序的基础上进行的在排序,且这两个的区间还没有合并,所以无论怎么打乱,都可以保证左半边元素的 \(a\) 小于等于右半边元素的 \(a\)。
对于第三维,相当于到了我们找逆序对的环节了,我们有归并排序和树状数组两种方法,但由于归并排序已经放到前面去处理第二维了,所以我们用树状数组来处理第三维,将节点依次插入树状数组,统计。
bool cmp2(node a, node b) {
if (a.y == b.y) {
return a.z < b.z;
}
return a.y < b.y;
}
void add(int u, int w) {
for (int i = u; i <= mx; i += lowbit(i)) {
t[i] += w;
}
}
int ask(int u) {
int sum = 0;
for (int i = u; i; i -= lowbit(i)) {
sum += t[i];
}
return sum;
}
void cdq(int l, int r) {
if (l == r) return ;
int mid = (l + r) >> 1;
cdq(l, mid);
cdq(mid + 1, r);
sort(s2 + l, s2 + mid + 1, cmp2);
sort(s2 + mid + 1, s2 + r + 1, cmp2);
int i, j = l;
for (i = mid + 1; i <= r; ++ i) {
while (s2[i].y >= s2[j].y && j <= mid) { // 一旦不符合,先统计,然后右指针右移一位。
add(s2[j].z, s2[j].cnt); // 插入
++ j;
}
s2[i].ans += ask(s2[i].z);
}
for (i = l; i < j; ++ i) { // 清空数组,memset 常数太大。
add(s2[i].z, -s2[i].cnt);
}
}
最后就是处理答案了,完整代码:
/*
The code was written by yifan, and yifan is neutral!!!
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define lowbit(i) (i & (-i))
template<typename T>
inline T read() {
T x = 0;
bool fg = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
const int N = 1e5 + 5;
int n, k, mx, m;
int t[N << 1], res[N];
struct node {
int x, y, z, cnt, ans;
} s1[N], s2[N];
bool cmp1(node a, node b) {
if (a.x == b.x) {
if (a.y == b.y) {
return a.z < b.z;
}
else return a.y < b.y;
}
return a.x < b.x;
}
bool cmp2(node a, node b) {
if (a.y == b.y) {
return a.z < b.z;
}
return a.y < b.y;
}
void add(int u, int w) {
for (int i = u; i <= mx; i += lowbit(i)) {
t[i] += w;
}
}
int ask(int u) {
int sum = 0;
for (int i = u; i; i -= lowbit(i)) {
sum += t[i];
}
return sum;
}
void cdq(int l, int r) {
if (l == r) return ;
int mid = (l + r) >> 1;
cdq(l, mid);
cdq(mid + 1, r);
sort(s2 + l, s2 + mid + 1, cmp2);
sort(s2 + mid + 1, s2 + r + 1, cmp2);
int i, j = l;
for (i = mid + 1; i <= r; ++ i) {
while (s2[i].y >= s2[j].y && j <= mid) {
add(s2[j].z, s2[j].cnt);
++ j;
}
s2[i].ans += ask(s2[i].z);
}
for (i = l; i < j; ++ i) {
add(s2[i].z, -s2[i].cnt);
}
}
int main() {
n = read<int>(), k = read<int>();
mx = k;
for (int i = 1, x, y, z; i <= n; ++ i) {
x = read<int>(), y = read<int>(), z = read<int>();
s1[i].x = x, s1[i].y = y, s1[i].z = z;
}
sort(s1 + 1, s1 + n + 1, cmp1);
int top = 0;
for (int i = 1; i <= n; ++ i) {
++ top;
if (s1[i].x != s1[i + 1].x || s1[i].y != s1[i + 1].y || s1[i].z != s1[i + 1].z) {
s2[++ m].x = s1[i].x;
s2[m].y = s1[i].y;
s2[m].z = s1[i].z;
s2[m].cnt = top;
top = 0;
}
}
cdq(1, m);
for (int i = 1; i <= m; ++ i) {
res[s2[i].ans + s2[i].cnt - 1] += s2[i].cnt;
}
for (int i = 0; i < n; ++ i) {
printf("%d\n", res[i]);
}
return 0;
}
P5094 [USACO04OPEN] MooFest G 加强版
一道比较好的入门题。统计答案的时候稍微麻烦一些。
/*
The code was written by yifan, and yifan is neutral!!!
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
template<typename T>
inline T read() {
T x = 0;
bool fg = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
const int N = 5e4 + 5;
int n;
ll ans;
struct node {
ll v, x;
} g[N];
bool cmp1(node a, node b) {
return a.v < b.v;
}
bool cmp2(node a, node b) {
return a.x < b.x;
}
void cdq(int l, int r) {
if (l == r) return ;
int mid = (l + r) >> 1;
cdq(l, mid);
cdq(mid + 1, r);
sort(g + l, g + mid + 1, cmp2);
sort(g + mid + 1, g + r + 1, cmp2);
ll sum1 = 0, sum2 = 0;
for (int i = l; i <= mid; ++ i) {
sum2 += g[i].x;
}
for (int i = mid + 1, j = l; i <= r; ++ i) {
while (j <= mid && g[j].x < g[i].x) {
sum1 += g[j].x;
sum2 -= g[j].x;
++ j;
}
int cnt1 = j - l, cnt2 = mid - j + 1;
ans = ans + (cnt1 * g[i].x - sum1 + sum2 - cnt2 * g[i].x) * g[i].v;
}
}
int main() {
n = read<int>();
for (int i = 1; i <= n; ++ i) {
ll v = read<ll>(), x = read<ll>();
g[i] = node{v, x};
}
sort(g + 1, g + n + 1, cmp1);
cdq(1, n);
cout << ans << '\n';
return 0;
}
「学习笔记」CDQ分治的更多相关文章
- 「学习笔记」FFT 快速傅里叶变换
目录 「学习笔记」FFT 快速傅里叶变换 啥是 FFT 呀?它可以干什么? 必备芝士 点值表示 复数 傅立叶正变换 傅里叶逆变换 FFT 的代码实现 还会有的 NTT 和三模数 NTT... 「学习笔 ...
- 「学习笔记」Min25筛
「学习笔记」Min25筛 前言 周指导今天模拟赛五分钟秒第一题,十分钟说第二题是 \(\text{Min25}\) 筛板子题,要不是第三题出题人数据范围给错了,周指导十五分钟就 \(\text{AK ...
- 「学习笔记」FFT 之优化——NTT
目录 「学习笔记」FFT 之优化--NTT 前言 引入 快速数论变换--NTT 一些引申问题及解决方法 三模数 NTT 拆系数 FFT (MTT) 「学习笔记」FFT 之优化--NTT 前言 \(NT ...
- 「学习笔记」Treap
「学习笔记」Treap 前言 什么是 Treap ? 二叉搜索树 (Binary Search Tree/Binary Sort Tree/BST) 基础定义 查找元素 插入元素 删除元素 查找后继 ...
- 「学习笔记」字符串基础:Hash,KMP与Trie
「学习笔记」字符串基础:Hash,KMP与Trie 点击查看目录 目录 「学习笔记」字符串基础:Hash,KMP与Trie Hash 算法 代码 KMP 算法 前置知识:\(\text{Border} ...
- 「学习笔记」平衡树基础:Splay 和 Treap
「学习笔记」平衡树基础:Splay 和 Treap 点击查看目录 目录 「学习笔记」平衡树基础:Splay 和 Treap 知识点 平衡树概述 Splay 旋转操作 Splay 操作 插入 \(x\) ...
- 「学习笔记」wqs二分/dp凸优化
[学习笔记]wqs二分/DP凸优化 从一个经典问题谈起: 有一个长度为 \(n\) 的序列 \(a\),要求找出恰好 \(k\) 个不相交的连续子序列,使得这 \(k\) 个序列的和最大 \(1 \l ...
- 【学习笔记】CDQ分治(等待填坑)
因为我对CDQ分治理解不深,所以这篇博客只是我现在的浅显理解有任何不对的,希望大佬指出. 首先就是CDQ分治适用的题型: (1)带修改,但修改互相独立 (2)必须允许离线 (3)解决数据结构的题,能把 ...
- 「学习笔记」斜率优化dp
目录 算法 例题 任务安排 题意 思路 代码 [SDOI2012]任务安排 题意 思路 代码 任务安排 再改 题意 思路 练习题 [HNOI2008]玩具装箱 思路 代码 [APIO2010]特别行动 ...
- 「学习笔记」ST表
问题引入 先让我们看一个简单的问题,有N个元素,Q次操作,每次操作需要求出一段区间内的最大/小值. 这就是著名的RMQ问题. RMQ问题的解法有很多,如线段树.单调队列(某些情况下).ST表等.这里主 ...
随机推荐
- [Git/SVN] Gitee使用教程
1 Gitee Gitee 提供免费的 Git 仓库,还集成了代码质量检测.项目演示等功能. 对于团队协作开发,Gitee 还提供了项目管理.代码托管.文档管理的服务,5 人以下小团队免费. CASE ...
- [网络]内网IP的判别与分类
1 内网IP划分 内网IP地址分为A类.B类和C类,其地址范围如下: A类地址: 10.0.0.0 - 10.255.255.255 B类地址: 172.16.0.0 - 172.31.255.255 ...
- Android开发_记事本(1)
一些知识 Textview TextView中有下述几个属性: id:为TextView设置一个组件id,根据id,我们可以在Java代码中通过findViewById()的方法获取到该对象,然后进行 ...
- 我的第一个项目(十二) :分数和生命值的更新(后端增删查改的"改")
好家伙,写后端,这多是一件美逝. 关于这个项目的代码前面的博客有写 我的第一个独立项目 - 随笔分类 - 养肥胖虎 - 博客园 (cnblogs.com) 现在,我们登陆进去了,我开始和敌人战斗,诶 ...
- 【机器学习与深度学习理论要点】11.什么是L1、L2正则化?
机器学习中几乎都可以看到损失函数后面会添加一个额外项,常用的额外项一般有两种,一般英文称作 L1-norm 和L2-norm,中文称作 L1正则化 和 L2正则化,或者 L1范数 和 L2范数.L1正 ...
- python库Munch的使用记录
开头 日常操作字典发现发现并不是很便利,特别是需要用很多 get('xxx','-') 的使用,就觉得很烦,偶然看到Kuls大佬公众号发布的一篇技术文有对 python munch库的使用, 使得字典 ...
- vue移动端适配方案
一.安装postcss-px-to-viewport插件 1.使用npm安装 $ npm install postcss-px-to-viewport --save-dev 2.或者使用yarn安装 ...
- 2023-03-13:给定一个整数数组 A,坡是元组 (i, j),其中 i < j 且 A[i] <= A[j], 这样的坡的宽度为 j - i。 找出 A 中的坡的最大宽度,如果不存在,返回 0
2023-03-13:给定一个整数数组 A,坡是元组 (i, j),其中 i < j 且 A[i] <= A[j], 这样的坡的宽度为 j - i. 找出 A 中的坡的最大宽度,如果不存在 ...
- 2020-12-23:如何保证MQ的高可用?
福哥答案2020-12-23: ActiveMQ:Master-Slave 部署方式主从热备,方式包括通过共享存储目录来实现(shared filesystem Master-Slave).通过共享数 ...
- 2021-04-15:给定一个由字符串组成的数组strs,必须把所有的字符串拼接起来,返回所有可能的拼接结果中,字典序最小的结果。
2021-04-15:给定一个由字符串组成的数组strs,必须把所有的字符串拼接起来,返回所有可能的拼接结果中,字典序最小的结果. 福大大 答案2021-04-15: "b"和&q ...