首页 > 其他分享 >「学习笔记」CDQ分治

「学习笔记」CDQ分治

时间:2023-07-02 09:00:24浏览次数:45  
标签:ch int s2 s1 分治 mid 笔记 read 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;
}

标签:ch,int,s2,s1,分治,mid,笔记,read,CDQ
From: https://www.cnblogs.com/yifan0305/p/17495479.html

相关文章

  • Python基础语法--课程笔记
    Smiling&Weeping----很难再爱上下一个春天只守着我的枯木 一等再等保留标识符:1.__*__代表系统定义函数的名字:__new__()  #创建新对象的函数__init__() #创建函数2.“_”在交互式执行中使用,代表计算结果,如......
  • Java官方笔记13集合
    StoringDataTheCollectionsFrameworkisthemostwidelyusedAPIoftheJDK.集合不是数据类型,它是JDK的API,可以用来存储数据等,相当于数据结构。theCollectionsFrameworkisasetofinterfacesthatmodelsdifferentwayofstoringdataindifferenttypesofco......
  • 莫队学习笔记
    引入问题给出一个长度为\(n\)的数组\(A\),接下来\(q\)次询问,每次询问求\([l,r]\)中有多少组\((i,j,k)\)使得\(a_i=a_j=a_k(i<j<k)\)。保证\(1\leqn\leq10^5,1\leqA_i\leqn(1\leqi\leqn)\)。莫队的基础思想——区间转移简单分析问题,貌似并没有可加性,所以分块和......
  • Vim学习笔记2--录制宏,调用宏
    1.VIM编辑器--录制宏调用宏录制宏qa进入宏记录模式,a为宏名shift+w移到词首i.escshift+ei()escq退出宏记录调用宏@a使用宏名为a的宏@前加数字表示重复操作的次数 2.VIM编辑器--文本替换r替换:1,$s;a;b;gc(:1,$sa;b;gc)高级进阶用法:100,200s/1/2/gc含义:vim......
  • C语言笔记:第3章 数据和C
    C语言中,数据类型可以分为基本数据类型、构造数据类型、指针数据类型、空类型四大类: 基本类型介绍如下: 整型数据是指不带小数的数字(int,shortint,longint,unsignedint,unsignedshortint,unsignedlongint):  转义列表: ......
  • 【狂神说Java】Java零基础学习笔记-预料
    【狂神说Java】Java零基础学习笔记-预料预料01:学习准备:博客博客,英文名为Blog,它的正式名称为网络日记为什么要写博客?需要总结和思考。有时候我们一直在赶路,却忘了放慢脚步提升文笔组织能力提升学习总结能力提升逻辑思维能力帮助他人,结交朋友冰冻三尺非一日之寒,写......
  • 第二周笔记
     ......
  • Linux学习笔记
    Linux命令ls 查看文件夹下的文件cd 切换路径pwd 查看当前所在的路径位置.. 上层目录mkdir 创建文件夹touch 创建文件且要指定后缀cat 查看文件内容more 查看文件内容(支持翻页[没试过])rm 删除文件 (删除文件夹使用rm-r)cp 复制文件rm 移动文件(移动文件谨慎使......
  • 图论:图的概念、存储和遍历 学习笔记
    图论图的概念从数据结构的角度看,图可以看作一个多对多的数据存储结构。而结合图论算法,图就可以成为很多问题的载体。图论是数据结构与算法结合的产物。OIWiki上给出的图相关概念比较全面,但是因为OI是民科各个地方的一些定义都不太一样,所以作大概了解即可。图的存储图的存......
  • web开发实训-学习笔记
    微信小程序属于前端前端开发工程师必须要实现相似竞品(快应用华为)具体开发能实现的功能首页的轮播图搜索界面能搜索的几首歌曲点击可播放,封面可以转动能自主的切换上下歌曲WXMLview=div打上{}的数据都是从外部1调取的数据'app.js'最主要的开发界面"color":"#ff......