首页 > 其他分享 >[学习笔记] CDQ分治

[学习笔记] CDQ分治

时间:2023-03-21 20:57:41浏览次数:43  
标签:const int 分治 mid 笔记 num CDQ

引入 - 分治

分治,就是将讲原问题不断细分直到规模小到能够解决,然后一层层向上合并得到答案的过程。

归并排序

大致思想:把序列拆成左右两部分,分别归并排序,然后使用两个指针按序合并左右部分。

归并求逆序对

归并求逆序对是分治的一个经典例子。

要做的就是在合并过程中计算逆序对对数。

由于合并的是两个有序数组,那么当 \(num_{left}>num_{right}\) 时,\(num_{left},num_{left+1},...,num_{mid}\) 显然都是大于 \(num_{right}\) 的,那么就可以直接统计对数。

代码实现

const int N = 500010;
int n, a[N], tmp[N];
ll ans;
void merge(int l, int r) {
	if (l == r) return ;
	int mid = l + r >> 1;
	merge(l, mid), merge(mid + 1, r);
	int lf = l, rt = mid + 1, now = l;
	while (lf <= mid && rt <= r) {
		if (a[lf] <= a[rt]) tmp[now++] = a[lf++];
		else tmp[now++] = a[rt++], ans += (mid - lf + 1);
	}
	while (lf <= mid) tmp[now++] = a[lf++];
	while (rt <= r) tmp[now++] = a[rt++];
	for (int i = l; i <= r; i++) a[i] = tmp[i];
}
int main() {
	n = read();
	for (int i = 1; i <= n; i++) a[i] = read();
	merge(1, n);
	printf("%lld\n", ans);
	return 0;
}

可以发现在合并过程中,加上了左段对右段的贡献。这是CDQ分治的一个典型过程。

CDQ分治

优点:常数较小,可代替高级数据结构,易实现。

缺点:必须离线。

二维偏序

给 \(n\) 个元素,第 \(i\) 个元素有 \(a_i,b_i\) 两个属性,设 \(f(i)\) 表示满足 \(a_j<a_i\) 且 \(b_j<b_i\) 的 \(j\) 的数量。

对于 \(d \in [0,n)\) ,求 \(f(i)=d\) 的数量。

思路

把第 \(i\) 个元素看成 \((a_i,b_i)\) 的一个点,那么求的就是以该点为左上角的矩形包含多少点。

可以先按 \(x(a_i)\) 排个序,用树状数组维护,\(sum_i\) 表示已经考虑过的点中 \(y\) 小于等于 \(y(b_i)\) 的点的个数。

代码

HDU1541 Stars

这题题面没说有多组数据,但是会WA。

const int N = 15010, M = 32010;
int n, mx;
struct point {
	int x, y;
	bool operator <(const point &b) {return x == b.x ? y < b.y : x < b.x;}
} a[N];
int sum[M];
void add(int x, int p) {
	while (x <= mx) {
		sum[x] += p;
		x += lowbit(x);
	}
}
int query(int x) {
	int res = 0;
	while (x) {
		res += sum[x];
		x -= lowbit(x);
	}
	return res;
}
int ans[N];
int main() {
	while (cin >> n) {
		memset(sum, 0, sizeof(sum)), memset(ans, 0, sizeof(ans));
		mx = 0;
		for (int i = 1; i <= n; i++) a[i].x = read() + 1, a[i].y = read() + 1, mx = max(mx, a[i].y);
		sort(a + 1, a + 1 + n);
		for (int i = 1; i <= n; i++) {
			ans[query(a[i].y)]++;
			add(a[i].y, 1);
		}
		for (int i = 0; i < n; i++) printf("%d\n", ans[i]);
	}
	return 0;
}

三维偏序

有 \(n\) 个元素,第 \(i\) 个元素有 \(a_i,b_i,c_i\) 三个属性,设 \(f(i)\) 表示满足 \(a_j\leq a_i\) 且 \(b_j\leq b_i\) 且 \(cj\leq c_i\) 且 \(j\neq i\) 的 jj 的数量。

对于 \(d\in[0,n)\),求 \(f(i)=d\) 的数量。

思路

按 \(a\) 排序,归并 \(b\),树状数组维护 \(c\)。

代码实现

const int N = 100010;
int n, k;
struct node {
	int a, b, c, cnt, ans;
} a[N], tmp[N];
bool cmp(const node &a, const node &b) {
	if (a.a != b.a) return a.a < b.a;
	if (a.b != b.b) return a.b < b.b;
	return a.c < b.c;
}
int sum[N << 1 | 1];
void add(int x, int p) {
	while (x <= N << 1) {
		sum[x] += p;
		x += lowbit(x);
	}
}
int query(int x) {
	int res = 0;
	while (x) {
		res += sum[x];
		x -= lowbit(x);
	}
	return res;
}
ll ans[N];
queue <int> q;
void work(int l, int r) {
	if (l == r) return ;
	int mid = l + r >> 1;
	work(l, mid), work(mid + 1, r);
	int lf = l, rt = mid + 1, now = l;
	while (lf <= mid && rt <= r) {
		if (a[lf].b <= a[rt].b) add(a[lf].c, a[lf].cnt), q.push(lf), tmp[now++] = a[lf++];
		else a[rt].ans += query(a[rt].c), tmp[now++] = a[rt++];
	}
	while (lf <= mid) tmp[now++] = a[lf++];
	while (rt <= r) a[rt].ans += query(a[rt].c), tmp[now++] = a[rt++];
	while (q.size()) {
		int x = q.front(); q.pop();
		add(a[x].c, -a[x].cnt);
	}
	for (int i = l; i <= r; i++) a[i] = tmp[i];            
}
int main() {
	n = read(), k = read();
	for (int i = 1; i <= n; i++) a[i].a = read(), a[i].b = read(), a[i].c = read(), a[i].cnt = 1;
	sort(a + 1, a + 1 + n, cmp);
	int m = n; n = 0;
	for (int i = 1; i <= m; i++) {
		if (a[i].a == a[n].a && a[i].b == a[n].b && a[i].c == a[n].c) a[n].cnt++;
		else a[++n] = a[i];
	}
	work(1, n);
	for (int i = 1; i <= n; i++) ans[a[i].ans + a[i].cnt - 1] += a[i].cnt;
	for (int i = 0; i < m; i++) printf("%d\n", ans[i]);
	return 0;
}

参考资料

CDQ分治【分治(真得头疼)

[学习笔记]CDQ分治和整体二分

标签:const,int,分治,mid,笔记,num,CDQ
From: https://www.cnblogs.com/shiranui/p/17238988.html

相关文章

  • 学习记录:day03笔记
    一、数据类型为什么要对数据进行分类?1、现实中的数据就是自带类别属性的2、对数据进行分类可以节约内存存储空间、提高运行速度存储空间的单位:Bit比特存储1个......
  • 学习记录:day04笔记
    一、for循环语句循环:就是一种让代码反复执行的方式,从而达到想要的效果for循环一般会使用一个变量来引导循环的进行,这一变量叫做该循环的循环变量iindexfor循环的变......
  • 学习记录:day05笔记
    一、数组什么是数组:变量的组合,是一种批量定义相同类型变量的方式定义:类型名数组名[数量];intarr[5];注意:数组的内存空间是连续分配的,且数组的长度一旦确定就无......
  • 构建之法阅读笔记1
    一、我过去是怎么做的  过去,刚开始学C时,我还不知道这些编程语言能干什么用,而且老师也只是只讲课本知识,动手实践很少,导致现在回想大一时并没有什么收获可以回味。加上......
  • 学习记录:day06笔记
    一、Window下获取方向键1、导入头文件#include<conio.h>2、通过getch()获取键盘上的键值上:72下:80左:75右:77 二、Linux下获取方向键:1、在Window中把getch.h文......
  • 学习记录:day07笔记
    进制转换1、为什么使用二进制、八进制、十六进制?因为目前CPU只能识别高低两种电平,只能对二进制数据进行计算二进制虽然能够直接别计算机识别但是不方便人去书写和记......
  • react 官网学习笔记
    1.元素(html片段)和组件的关系(js函数)2.写组件的方式(function还是class)3.一个括号和两个括号的使用场景{}(获取值/js函数调用){{}}4.props和render都是做什......
  • 【笔记】electron + react + antd
    electronElectron是一个使用JavaScript、HTML和CSS构建桌面应用程序的框架。嵌入Chromium和Node.js到二进制的Electron允许您保持一个JavaScript代码代码库......
  • 王树森Transformer学习笔记
    目录TransformerAttention结构Self-Attention结构Multi-headSelf-AttentionBERT:BidirectionalEncoderRepresentationsfromTransformersSummaryReferenceTransformer......
  • 树链剖分学习笔记(1)
    两大DFS树链剖分是一个比较简单易懂的算法,其两个基础操作为两次dfs,第一次dfs求出每个节点的父节点(\(f_{i}\)),深度(\(dep_{i}\)),子树大小(\(size_{i}\)),重儿子(\(son_{i}\))。其......