首页 > 编程语言 >离线分治算法:cdq 分治

离线分治算法:cdq 分治

时间:2024-07-23 15:08:22浏览次数:9  
标签:return int 分治 离线 mid cdq 排序

\(\texttt{0x00}\):前置芝士

  1. 归并排序;
  2. 树状数组;
  3. 重载运算符(这个大概都会吧)。

\(\texttt{0x01}\):介绍

cdq 分治是一种离线分治算法,可用来处理以下几种问题:

  1. 解决和点对有关的问题。
  2. 1D 动态规划的优化与转移。
  3. 通过 CDQ 分治,将一些动态问题转化为静态问题。

它们的本质都是通过一系列转化将原问题转化为三维偏序问题。

\(\texttt{0x02}\):梦开始的地方

P3810 【模板】三维偏序(陌上花开)

我们都知道,求二维偏序(比如逆序对)可以用归并排序或树状数组,他们的本质都是先确定一维的顺序,再计算另一维贡献。

那拓展到三维偏序也是如此,既然归并和树状数组都能确定一维的顺序,那把他们两个合起来不就能做三维偏序了吗?

确实是这样的。

先回顾一下归并排序求逆序对。

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];
}

流程是这样的:将当前处理的序列一分为二,对于 \(i,j\) 在同一边的情况,递归求解即可,我们只需要考虑在两边的情况。

不妨设 \(i\) 在左,\(j\) 在右,由于左右区间已经递归按数值排好序了,下标一开始就是有序的,所以 \(i\) 的下标一定小于 \(j\) 的下标,又因为左右两边的数值是单调递增的,所以可以用双指针扫描一遍求出答案,具体的,当 \(a[i] > a[j]\) 时,说明 \(a[i]\) 是第一个大于 \(a[j]\) 的数且后面的数都大于 \(a[i]\),所以 \(a[j]\) 和 \(a[i\sim mid]\) 都构成逆序对,可以直接统计答案。

时间复杂度 \(O(n\log n)\)。

注意两个容易理解错的地方:

  1. 虽然说按照数值排序会打乱下标的顺序,但是排序是在区间内部进行的,也就是说,左右两部分是完全封闭地完成了排序,所以左边区间的下标一定小于右边区间内的下标;

  2. 由于分治是从下往上递归的,所以同区间内的两个数的贡献已经被算过,只用考虑跨区间的贡献。

现在在这个基础上加上一维,就再套个树状数组控制第三维就行了,具体说来,排序保证了第一维,归并保证了第二维,树状数组保证了第三维。

这道题还有一个魔鬼细节,就是这三维都可以取等号,也就是说假如 \(a,b\) 完全相同,\(a\) 在 \(b\) 左边,那么 \(f(a) = f(b) = 1\) 才对,但是 cdq 分治只会算左边给右边的贡献,所以实际算出来为 \(f(a) = 0,f(b) = 1\)。

先去重,思考一下不难发现假如有 \(k\) 个数都相同,那么树状数组中的所有修改都要从 \(1\) 变成 \(k\),同时在最后还要加上内部的贡献 \(k - 1\)。

时间复杂度 \(O(n\log ^2n)\)。

\(\texttt{Code}\):

#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;
}

标签:return,int,分治,离线,mid,cdq,排序
From: https://www.cnblogs.com/Brilliant11001/p/18318477

相关文章

  • 分治法 -----归并排序、逆序对、最大子数组和
    一分治法概念分治(divide-and-conquer),顾名思义分而治之。分治的核心是将原问题分解为同类型的规模更小的子问题,子问题往往可以求解或者求解比较简单,通过整合子问题的解得到原来问题的解。分治的过程可以用如下图来表示:由上述图示可发现整个分治过程是一颗树,而且子问题的处......
  • 将 conda 环境跨平台离线移动
    我目前正在Windows上使用Anaconda。我想将虚拟环境移动到我的另一个系统,该系统使用linux作为操作系统。问题是linux系统无法访问互联网,所以我需要以某种方式从windows系统下载所有独立安装文件并将它们移动到linux系统。我该如何应对这个问题?这是一个补充问题,但我也遇到了困......
  • 线段树分治
    线段树分治线段树分治可以解决这样的问题:对于一些操作,每个操作只在\(l\)~\(r\)的的时间内有效。对于一些操作,询问某个时间点所有操作的贡献。经典例题算法思路对于这道题,因为二分图的充要条件是不存在奇环,所以可以使用扩展域并查集解决。我们考虑离线优化:对于每......
  • 二次离线莫队
    更新答案不是\(O(1)\)?答案可差分?二离来啦。P4887【模板】莫队二次离线先考虑贡献:\(f(x,[l,r])\)表示\(x\)对区间\([l,r]\)。考虑莫队每次的移动:\(r\tor'\)。答案增加为:\[\sum_{i\in[r+1,r']}f(i,[l,i-1])=\sum_{i\in[r+1,r']}f(i,[1,i-1])-f(i,[1,l-1])\]发现前......
  • pycharm如何在离线状态下设置成中文
    手动安装汉化包官方汉化包地址:https://plugins.jetbrains.com/plugin/13710-chinese-simplified-language-pack----/versions 1、点击链接进入官方汉化包下载页      2、点击versions,找到和自己pycharm相同年份/版本的汉化包,后点击Download,浏览器会自动下载。 3......
  • Linux环境离线安装docker&docker-compose(包含一键安装脚本和一键安装包)
    一、docker离线安装1、下载docker离线安装包下载最新版本的docker(或者选择自己想要安装的版本)到本地。1)docker下载地址:Docker版本获取备注:此地址自2024年7月无法访问下载docker版本,小编已经将可以使用的docker、docker-compose版本整理在百度网盘中如有需要可以自行获取......
  • 【离线】- 莫队
    前言莫队是由莫涛提出的一种离线算法,是分块与双指针的结合,一般可以在\(O(n\sqrtn)\)或者\(O(n\sqrtm)\)的复杂度解决一些种类问题。普通莫队SP3267DQUERY-D-query给你一个长度为\(n\)的数列\(A\),\(m\)次询问,每次询问区间\([l,r]\)内的不同数字的个数。如果......
  • 百度人脸识别Windows C++离线sdk C#接入
    百度人脸识别WindowsC++离线sdkC#接入目录说明设计背景•场景特点:•客户特点:•核心需求:SDK包结构效果代码说明自己根据SDK封装了动态库,然后C#调用。功能接口设计背景•场景特点:--网络:对于无网、局域网等情况,无法连接公网,API方式无法运作。如政府单......
  • CDQ分治
    该分治算法由CDQ提出,主要用于解决三维偏序问题。下面的内容就三维偏序例题来讲。题目给你一个序列,每个元素有\(a,b,c\)三个属性,问满足\(a_i>a_j,b_i>b_j,c_i>c_j\)的数对\(i,j\)的数量。分析将原序列按照\(a\)值排序,将其变为下标。CDQ分治的主要步骤是对于一个需要......
  • CDQ 分治学习笔记
    CDQ分治的流程大致是将对于区间\([l,r]\)中点\(x,y\)的计数分为两类处理:\(x,y\)均位于\([l,mid]\)或\([mid+1,r]\)中,这样的点对贡献可以递归解决。\(x,y\)分别位于\([l,mid]\)和\([mid+1,r]\)中,这样的点对通过一些操作来统计贡献。显然这两类贡献之和即为......