浅谈cdq分治

这个东东是HN著名女选手陈丹琦发明的。

嗯……

其实还是挺简单的吧,就是把一个区间分成两部分,然后看前半部分对后半部分的贡献,然后一路分治下去就好了。

首先扔一个简单的题目:https://www.luogu.org/problemnew/show/P1908

就是求逆序对,刚开始用树状数组在线写WA QAQ

然后发现是边界问题 QAQ

愉快地改完后用cdq写了一遍,1A!!!太感动了。

果然还是树状数组跑得快一点。。

那么就简单讲讲怎么做吧。

我们先每把当前序列分成两份,这就保证了左边部分的坐标是小于右边的嘛(显而易见),当我们分治下去的时候可以 随便做归并排序,那么对于两个有序序列,我们可以用two pointers来乱搞,那么就可以搞出来了呀。

看代码吧:

#include<bits/stdc++.h>
#define N 500005
using namespace std;
int sz, n, a[N], b[N], A[N], B[N], C[N], ans[N];
long long s;
void cdq(int l, int r){
	if(l == r) return;
	int mid = (l + r) >> 1;
	cdq(l, mid); cdq(mid + 1, r);//分治下去
	int sz1 = 0, sz2 = 0, sz3 = 0, p = 0;
	for(int i = l; i <= mid; i ++) A[++ sz1] = b[i]; //先拿出来,方便处理,A为左边的部分,B为右边的部分
	for(int i = mid + 1; i <= r; i ++) B[++ sz2] = b[i];
	
	for(int i = 1; i <= sz2; i ++){
		for(;A[p + 1] <= B[i] && p < sz1;) C[++ sz3] = A[++ p];//C是临时用来储存A、B合并的有序数组
		ans[i + mid] += sz1 - p;//即左边对当前为i的贡献
		C[++ sz3] = B[i];
	}
	for(;p < sz1; ) C[++ sz3] = A[++ p];//边界处理一下
	for(int i = l; i <= r; i ++) b[i] = C[i - l + 1];//把有序数组反赋回去
} 
int main(){
	scanf("%d", &n);
	for(int i = 1; i <= n; i ++) scanf("%d", &b[i]), a[i] = b[i];
	sort(b + 1, b + 1 + n);
	int sz = unique(b + 1, b + 1 + n) - b;
	for(int i = 1; i <= n; i ++) a[i] = lower_bound(b + 1, b + 1 + n, a[i]) - b;//离散化
	for(int i = 1; i <= n; i ++) b[i] = a[i]; 
	cdq(1, n);
	for(int i = 1; i <= n; i ++) s += ans[i];//累计答案
	printf("%lld", s);
	return 0;
}//愉快的结束

不过瘾,再来一题:https://www.luogu.org/problemnew/show/P3810

这就是经典的三维偏序

同上题,分治减少一维,再用树状数组做剩下两维。

代码:

#include<bits/stdc++.h>
#define N 400005
#define lowbit(x) x & -x
using namespace std;
struct cdq_is_good{
	int a, b, c, id, idd;
}a[N], A[N];
bool cmp(cdq_is_good x, cdq_is_good y){
	if(x.c != y.c)return x.c < y.c;
	if(x.b != y.b)return x.b < y.b;
	return x.a < y.a;
}
int tree[N];
void update(int x, int y){
	for(;x < N;x += lowbit(x)) tree[x] += y;
}
int query(int x){
	int ret = 0;
	for(;x;x -= lowbit(x)) ret += tree[x];
	return ret;
}
int del[N], ans[N], f[N], n, k;
void cdq(int l, int r){
	if(l == r) return;
	int mid = (l + r) >> 1, p = l - 1, sz = 0, delsz = 0;
	cdq(l, mid); cdq(mid + 1, r);
	for(int i = mid + 1; i <= r; i ++){
		for(;a[p + 1].b <= a[i].b && p < mid;){A[++ sz] = a[++ p]; update(a[p].a, 1); del[++ delsz] = a[p].a;}//树状数组处理剩下两维
		ans[a[i].id] += query(a[i].a);
		A[++ sz] = a[i];
	}
	for(;p < mid;) A[++ sz] = a[++ p];
	for(int i = l; i <= r; i ++) a[i] = A[i - l + 1];
	for(int i = 1; i <= delsz; i ++) update(del[i], - 1);//这样可以避免不必要的时间开销,就像树分治一样,千万不能用memset
}
int main(){
	scanf("%d%d", &n, &k);
	for(int i = 1; i <= n; i ++) scanf("%d%d%d", &a[i].a, &a[i].b, &a[i].c), a[i].id = a[i].idd = i;
	sort(a + 1, a + 1 + n, cmp);//先排序去掉一维, 先去 c 
	int l = 1;
	for(int i = 1; i <= n + 1; i ++){
		if(!(a[i].a == a[l].a && a[i].b == a[l].b && a[i].c == a[l].c)){//处理相同的情况
			for(;l < i;) a[l ++].idd = a[i - 1].id;
		}
	}
	cdq(1, n);
	for(int i = 1; i <= n; i ++) f[ans[a[i].idd]] ++;
	for(int i = 0; i < n; i ++) printf("%d\n", f[i]);
	return 0;
}

这就可以了,cdq分治还是蛮简单的嘛。

哈哈哈哈哈哈哈…… 

 

 

posted @ 2018-10-01 22:23  lahlah  阅读(58)  评论(0)    收藏  举报