浅谈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分治还是蛮简单的嘛。
哈哈哈哈哈哈哈……