bzoj[3262]陌上花开==洛谷P3801 (CDQ分治+树状数组)
题意:三维偏序。
给你N个花,每个花有三个属性。一个花的三个属性都分别大于等于另一朵花的时候,等级+1,问从0到N-1级,每一级有多少朵花。
题解:这题目可以树套树,CDQ套CDQ,KD树等等。这里用CDQ分治+树状数组来解题目和解释。这里花的三个我用x,y,z来表示
总的来讲就是,第一维度就直接sort排序,第二维度用CDQ分治来维护,最后,第三维度用树状数组来维护。
这里是的树状数组是权值树状数组
A:第一维度的值我们用SORT来维护之后,即排完序之后,我们把第二维度的值给分成两个部分,[L,MID]和[MID+1,R],因为第一维度已经排过序列了,所以[L,MID]里面所有的x都比[MID+1,R]要大。
B:所以,我们把[L,MID]都先打一个标记F=-1。然后把数组按照y从小到大排序,之后遍历一遍,当发现这个F的标记是-1的时候,我们知道这个值的X一定在X小的那块,我们把这个花的Z值给加到树状数组中,然后遇到不是-1的东西的时候,我们在树状数组中查询一遍这个值的前缀和。
C:因为在打标记的时候,我们已经能确定哪些值的X会是小的(标记成-1),然后CDQ排序后,遍历的时候,我们也能确定这些Y值会是从小到大的,那么我们最后只需要向二维偏序那样去确定,比当前这个点的Z值还要小的,花的Z值有多少个。
D:最后类似归并排序那样递归深搜,就OK了。
E:注意CDQ分治并不能去确定重复一样的值,所以我们要离散化去重,而且在比较大小排序的时候,一定要写完整。因为这两个原因,笔者WA了一下午。我太菜了。。
F:若还是迷迷糊糊的,可以结合代码+注释阅读。
PS:这个CDQ分治主要是一种思想吧,计算左边的区间对于右区间的贡献。CDQ分治也是基于归并排序的一种变形和利用吧。
据说CDQ分治的发明人和莫队和主席树的发明人同一届,什么神仙啊。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mx = 2e6 + 7;
int bit[mx * 4];
int ans[mx];
int ans2[mx];
int hasha[mx]={0};
struct node {
int x, y, z, id;
int cnt,f;
} a[mx], b[mx],d[mx];
int lowbit(int x) {
return x & (-x);
}
void add(int i, int x) {
while (i <= mx) {
bit[i] += x;
i += lowbit(i);
}
}
int sum(int i) {
int s = 0;
while (i > 0) {
s += bit[i];
i -= lowbit(i);
}
return s;
}
bool cmpx(node s, node t) { //一定要写详细,WA到自闭
if (s.x == t.x) {
if (s.y == t.y) {
if (s.z == t.z)
return s.id < t.id;
else
return s.z < t.z;
} else
return s.y < t.y;
} else
return s.x < t.x;
}
bool cmpy(node s, node t){ //一定要写详细,WA到自闭
if (s.y == t.y) {
if (s.z == t.z) {
if (s.x == t.x)
return s.id < t.id;
else
return s.x < t.x;
} else
return s.z < t.z;
} else
return s.y < t.y;
}
void cdq(int l, int r) {
if (l >= r)
return;
int mid = (l + r) >> 1;
int cnt = 0;
for (int i = l; i <= r; i++) {
cnt++;
b[cnt] = d[i];
if (i <= mid)
b[cnt].f = -1; //给前半部分小的加上标记,表示这些花的X都是比较小的
else
b[cnt].f = d[i].id;
}
sort(b + 1, b + 1 + cnt, cmpy); //sort y的值,这样就能保证y值从小到大遍历
for (int i = 1; i <= cnt; i++) {
if (b[i].f == -1)
add(b[i].z, b[i].cnt); //把x值小的那一半加入树状数组,这个cnt表示重复的个数
else
ans[b[i].id] += sum(b[i].z); //如果是大的,则是查询前缀,就是比这个Z的X和Y都小的有几个,x靠标记已经确定,而Y是利用这个排序可以确定小的。
}
for (int i = 1; i <= cnt; i++)
if (b[i].f == -1)
add(b[i].z, -b[i].cnt); //因为要递归深搜所以注意,把树状数组这次操作过的地方清零
cdq(l, mid); //递归左右,类似归并,能做到不重不漏
cdq(mid + 1, r);
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
#ifdef ACM_LOCAL
freopen("input.txt", "r", stdin);
freopen("output.txt", "w", stdout);
#endif
memset(bit, 0, sizeof(bit));
int n, s;
cin >> n;
cin >> s;
for (int i = 1; i <= n; i++) {
cin >> a[i].x >> a[i].y >> a[i].z;
ans2[i] = ans[i] = 0;
}
sort(a + 1, a + 1 + n, cmpx); //因为sort过了,所以可以保证X一定会小于
int num = 0;
for (int i = 1; i <= n; i++) { //手撕一个unique去重
a[i].id = i;
if (i != 1 && a[i].x == a[i - 1].x && a[i].y == a[i - 1].y && a[i].z == a[i - 1].z)
d[num].cnt++;
else {
d[++num] = a[i];
d[num].cnt = 1; //记得标记相同的有多少个
}
}
cdq(1, num);
int res = 0;
for (int i = 1; i <= num; i++)
ans[d[i].id] += d[i].cnt - 1;
for (int i = 1; i <= num; i++) {
for (int j = 1; j <= d[i].cnt; j++)
ans2[++res] = ans[d[i].id];
} //离散化去重回来,逆过程
for (int i = 1; i <= res; i++)
hasha[ans2[i]]++;//cout << ans[i] << endl;
for (int i = 0; i <= n - 1; i++)
cout << hasha[i] << endl; //输出每个等级的花有多少
return 0;
}