CDQ分治学习笔记

前言

最近又做到了 \(CDQ\) 的题目,然鹅我又搞忘了……

╮(╯▽╰)╭

提前声明:因为博主很菜,很多知识都是从其他博客学来的,所以内容多有重复,请见谅!

那么我们一起去康康神仙 \(CDQ\) 的神仙算法吧~

介绍

众所周知,这是神仙 \(CDQ\) (陈丹琪)所发明的离线算法。 敬礼!

适用于解决多重要求的询问。

针对不同问题,可以分为多维偏序问题

一维偏序

其实一维偏序就差不多跟分治一个性质。而归并排序应该是分治里最具代表性的了,所以下面举归并排序为栗子。

Code

void q_sort(int l,int r) {
	if(l==r) return;
	int mid=(l+r)>>1;
	int i=l,j=mid+1,tot=l;
	q_sort(l,mid);
	q_sort(mid+1,r);
	while(i<=mid && j<=r) {
		if(a[i]<=a[j]) b[tot++]=a[i++];
		else {
			ans+=mid-i+1;
			b[tot++]=a[j++];
		}
	}
	while(i<=mid) b[tot++]=a[i++];
	while(j<=r) b[tot++]=a[j++];
	for(int k=l;k<=r;k++) a[k]=b[k];
}

上面的代码就是归并排序求逆序对(马蜂是以前的,很丑很无辜)。

其实就是将序列不断切分切分,切到只剩下一个时开始与相邻的序列开始比较合并,从而达到较好的排序效果。

因为归并是从短到长逐一合并的,合并前的序列已经是有序的,而合并的过程中是两组队列通过比较选择小的放进序列中,一直到比较完所有序列。

所以整个序列最终的有序性是可以保证的。

而逆序对则是利用合并中两个队列都是有序的性质。

如果 \(a_i > a_j\),就说明从 \(i\)\(mid\) 的所有数字都大于 \(a_j\),因此逆序对数量要加上 \(i\)\(mid\) 的长度 \(mid - i + 1\)

二维偏序

Desprition

给定 \(n\) 个元素,第 \(i\) 个元素有 \(a_i\)\(b_i\) 两个属性,设 \(f(i)\) 表示满足 \(a_j\leq a_i\)\(b_j\leq b_i\)\(j\) 的数量。对于 \(d\in [0,n]\),求满足 \(f(i)=d\) 的数量。

Solution

问题转化,可以将两个变量看成坐标轴上一个点的横纵坐标。那么问题就可以表示为如下的坐标系:

对于每个点,满足条件的结果就是该点与原点形成的矩阵内的点的数量。

所以我们可以先将任意一维从小到大排序,一个点的答案至于它前面的点有关(自动排除了一些不可能情况),然后第二维可以在一维的基础上用 树状数组 维护。每次在求完小于当前值得数量后,再在当前值的位置加一维护即可。

Code


#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1e5 + 5;
int n, m, tmp, tr[N], ans[N];
struct node {
	int x,y;
}a[N]; 
int lowbit(int x) {
	return x & (-x);
}
void add(int x,int v) {
	for(; x < N; x += lowbit(x)) tr[x] += v;
}
int query(int x) {
	int res = 0;
	for(; x; x -= lowbit(x)) res += tr[x];
	return res;
}
bool cmp(node a,node b) {
	return a.x == b.x ? a.y < b.y : a.x < b.x;
}
int main() {
	scanf("%d",&n);
	for(int i = 1; i <= n; i ++) scanf("%d %d",&a[i].x,&a[i].y);
	sort(a + 1,a + 1 + n,cmp);
	for(int i = 1; i <= n; i ++) {
		tmp = query(a[i].y + 1);
		ans[tmp] ++;
		add(a[i].y + 1,1); 
	}
	for(int i = 0; i < n; i ++) printf("%d\n",ans[i]);
	return 0;
}

三维偏序

可以想到将三个变量存在结构体里,第一维可以直接通过 \(sort\) 先排好序, 第二维使用归并排序, 第三维用树状数组维护。

因为前面提到了归并排序的性质(两个序列在合并前都是有序的),所以在一维排好序的前提下,我们可以保证 \(a_{l1}\)\(a_{mid1}\) 都满足小于等于 \(a_{(mid+1)1}\)\(a_{r1}\),然后再使用归并排序二维。

归并排序时 (\(l\leq i\leq mid\), \(mid+1\leq j\leq r\)), 如果正在比较 \(a_{i2}\)\(a_{j2}\)\(a_{i2} < a_{j2}\), 说明当前情况的前两位符合要求,直接用树状数组维护第三维。

否则不符合情况,当前树状数组位置上的数量就是答案的最大值。

需要注意的是,归并排序每次都会从头再来,因此会出现重复得加,所以在归并排序后要记得清空数组

例题

Code


#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1e5 + 5;
struct node {
	int a,b,c,w,f;
}t[N],e[N];
int n,k,cnt = 1,tr[N * 2],ans[N];
bool cmp(node x,node y) {
	if(x.a != y.a) return x.a < y.a;
	if(x.b != y.b) return x.b < y.b;
	return x.c < y.c;
}
int lowbit(int x) {return x & (-x);}
void add(int x,int val) {
	for(; x <= k; x += lowbit(x)) tr[x] += val;
}
int query(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) / 2;
	CDQ(l,mid);
	CDQ(mid + 1,r);
	int p = l,tot = l,q = mid + 1;
	while(p <= mid && q <= r) {
		if(t[p].b <= t[q].b) {
			add(t[p].c,t[p].w);
			e[tot++] = t[p++];
		}
		else {
			t[q].f += query(t[q].c);
			e[tot++] = t[q++];
		}
	}
	while(p <= mid) {
		add(t[p].c,t[p].w);
		e[tot++] = t[p++];
	}
	while(q <= r) {
		t[q].f += query(t[q].c);
		e[tot++] = t[q++];
	}
	for(int i = l; i <= mid; i ++) add(t[i].c,-t[i].w);
	for(int i = l; i <= r; i ++) {
		t[i] = e[i];
		//printf("%d %d %d %d %d %d\n",i,t[i].a,t[i].b,t[i].c,t[i].f,t[i].w);
	}
}
int main() {
	scanf("%d %d",&n,&k);
	for(int i = 1; i <= n; i ++) {
		scanf("%d %d %d",&t[i].a,&t[i].b,&t[i].c);
		t[i].w = 1;
	}
	sort(t + 1,t + 1 + n,cmp);
	for(int i = 2; i <= n; i ++) {
		if(t[i].a == t[cnt].a && t[i].b == t[cnt].b && t[i].c == t[cnt].c) t[cnt].w++;
		else t[++cnt] = t[i];
	}
//	printf("%d\n",cnt);
	CDQ(1,cnt);
	for(int i = 1; i <= cnt; i ++) ans[t[i].f + t[i].w - 1] += t[i].w;
	for(int i = 0; i < n; i ++) printf("%d\n",ans[i]);
	return 0;
}

四维偏序

需要 \(CDQ\)\(CDQ\) 再套树状数组的东东好变态。。。

可恶!

我亲切地把\(CDQ\)\(CDQ\)称作\(CDQ\)的平方,恩不错,生动形象地表现出了四维的思维跨度之大?

Desprition

给定 \(N\) 个有序四元组 \((a,b,c,d)\),求对于每一个四元组 \((a,b,c,d)\),有多少个四元组 \((a_2,b_2,c_2,d_2)\) 满足 \(a_2<a\) && \(b_2<b\) && \(c_2<c\) && \(d_2<d\)

Solution

大概过程:

  • 对第一维排序

  • 第二维 \(cdq\), 递归解决子问题

  • 再按第三维顺序合并

  • 用树状数组维护第四维

Code

那就咕了吧()

例题

更高维偏序

巨佬的博客没怎么讲,况且蒟蒻不会 \(bitset\) ,所以咱们咕一咕?

通过度娘,我了解到貌似现在最高达到了七维偏序,所以我果断放弃~

或许在我还活着的某一天我会想起来更的。。。

然后我死了……欧耶

CDQ 的拓展应用

我想要讲一下 \(cdq\) 套其他算法的应用~

咕咕咕~

参考博客

[学习笔记]CDQ分治和整体二分
CDQ分治学习笔记
【教程】简易CDQ分治教程&学习笔记
【教程】CDQ套CDQ——四维偏序问题

posted @ 2021-03-01 21:48  Spring-Araki  阅读(103)  评论(0编辑  收藏  举报