离线分治算法:cdq 分治
:前置芝士
- 归并排序;
- 树状数组;
- 重载运算符(这个大概都会吧)。
:介绍
cdq 分治是一种离线分治算法,可用来处理以下几种问题:
- 解决和点对有关的问题。
- 1D 动态规划的优化与转移。
- 通过 CDQ 分治,将一些动态问题转化为静态问题。
它们的本质都是通过一系列转化将原问题转化为三维偏序问题。
:梦开始的地方
我们都知道,求二维偏序(比如逆序对)可以用归并排序或树状数组,他们的本质都是先确定一维的顺序,再计算另一维贡献。
那拓展到三维偏序也是如此,既然归并和树状数组都能确定一维的顺序,那把他们两个合起来不就能做三维偏序了吗?
确实是这样的。
先回顾一下归并排序求逆序对。
void merge_sort(int l, int r) {
if(l == r) return ;
int mid = l + r >> 1;
merge_sort(l, mid);
merge_sort(mid + 1, r);
int i = l, j = mid + 1;
for(int k = l; k <= r; k++)
if(j > r || i <= mid && a[i] <= a[j]) tmp[k] = a[i++];
else tmp[k] = a[j++], res += mid - i + 1;
for(int k = l; k <= r; k++) a[k] = tmp[k];
}
流程是这样的:将当前处理的序列一分为二,对于
不妨设
时间复杂度
注意两个容易理解错的地方:
-
虽然说按照数值排序会打乱下标的顺序,但是排序是在区间内部进行的,也就是说,左右两部分是完全封闭地完成了排序,所以左边区间的下标一定小于右边区间内的下标;
-
由于分治是从下往上递归的,所以同区间内的两个数的贡献已经被算过,只用考虑跨区间的贡献。
现在在这个基础上加上一维,就再套个树状数组控制第三维就行了,具体说来,排序保证了第一维,归并保证了第二维,树状数组保证了第三维。
这道题还有一个魔鬼细节,就是这三维都可以取等号,也就是说假如
先去重,思考一下不难发现假如有
时间复杂度
#include <iostream>
#include <algorithm>
#define lowbit(x) x & -x
using namespace std;
const int N = 100010, M = 200010;
int n, vmax;
struct node{
int a, b, c, s, res; //s是数量,res 是去重后每个元素满足条件的个数
bool operator< (const node &o) const { //三关键字排序
if(a != o.a) return a < o.a;
if(b != o.b) return b < o.b;
return c < o.c;
}
bool operator== (const node &o) const {
return a == o.a && b == o.b && c == o.c;
}
}e[N], tmp[N];
int ans[N];
int tr[M];
void add(int x, int y) {
for(; x < M; x += lowbit(x)) tr[x] += y;
}
int ask(int x) {
int res = 0;
for(; x; x -= lowbit(x)) res += tr[x];
return res;
}
void cdq(int l, int r) {
if(l >= r) return ;
int mid = l + r >> 1;
cdq(l, mid), cdq(mid + 1, r); //递归左右两边
int i = l, j = mid + 1;
for(int k = l; k <= r; k++) {
if(j > r || i <= mid && e[i].b <= e[j].b) add(e[i].c, e[i].s), tmp[k] = e[i++]; //套一个树状数组
else e[j].res += ask(e[j].c), tmp[k] = e[j++];
}
for(int k = l; k <= mid; k++) add(e[k].c, -e[k].s); //做完一次后必须要清空树状数组
for(int k = l; k <= r; k++) e[k] = tmp[k]; //归并排序
}
int main() {
scanf("%d%d", &n, &vmax);
int a, b, c;
for(int i = 0; i < n; i++) {
scanf("%d%d%d", &a, &b, &c);
e[i] = {a, b, c, 1};
}
sort(e, e + n);
int tt = 1;
for(int i = 1; i < n; i++) { //去重
if(e[i] == e[tt - 1]) e[tt - 1].s++;
else e[tt++] = e[i];
}
cdq(0, tt - 1);
for(int i = 0; i < tt; i++)
ans[e[i].res + e[i].s - 1] += e[i].s; //注意加上内部贡献
for(int i = 0; i < n; i++) printf("%d\n", ans[i]);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】