学习上参考了这篇博客: pecco的笔记
cdq 的主要是想就是:
- 递归前一半
- 判断前一半对后一半的影响
- 递归后一半
但是需要注意,如果偏序关系带等号,对于完全相等的元素,需要捆绑起来一起处理,不然可能会把相等的元素划到左右两个区间去,从而把相等两元素的之间相互的贡献漏掉一半。
1.二维偏序
例: 处理逆序对
//nlog^2n
ll cdq(int A[], int n)
{
if (n == 1)
return 0;
int mid = n / 2, i = 0, j = mid;
ll ans = cdq(A, mid) + cdq(A + mid, n - mid);
sort(A, A + mid, greater<int>()), sort(A + mid, A + n, greater<int>());
for (; j < n; ++j)
{
while (i < mid && A[i] > A[j])
i++;
ans += i;
}
return ans;
}
//nlogn
ll cdq(int A[], int n)
{
static int B[MAXN]; // 暂存归并排序的结果
if (n == 1)
return 0;
int mid = n / 2, i = 0, j = mid, k = 0;
ll ans = cdq(A, mid) + cdq(A + mid, n - mid);
for (; j < n; ++j)
{
while (i < mid && A[i] > A[j])
B[k++] = A[i++];
ans += i, B[k++] = A[j];
}
while (i < mid)
B[k++] = A[i++];
memcpy(A, B, sizeof(int) * k);
return ans;
}
注意到, 这个同样可以用树状数组处理.
signed main()
{
int n; cin >> n;
vector<int>a(n);
for (int i = 0; i < n; ++i) {
cin >> a[i];
}
//离散化 a 数组
int ans = 0;
for (int i = 0; i < n; ++i) {
ans += i - ask(a[i]);
upd(a[i], 1);
}
cout << ans << endl;
}
3.三维偏序
先在外部按第一维排好序。在函数体内,递归地解决左区间和右区间的子问题,然后对于一个在左区间,一个在右区间的点对,我们分别将左右区间按第二维排序,第二维用双指针, 第三维用树状数组解决问题。
具体地,对于右区间的每个点,我们遍历左区间,把第二维符合要求的点的第三维塞入树状数组,然后查询第三维也符合要求的点的数目(即上树状数组求逆序对方法)。
为了节约时间,清空树状数组不使用memset
,而是原路删除。
eg: 当然也可以选择 cdq 套 cdq, 而不是 cdq 套树状数组
//https://www.luogu.com.cn/problem/P3810
bool cmpa(node x, node y) {
if (x.a == y.a) {
if (x.b == y.b)return x.c < y.c;
return x.b < y.b;
}
return x.a < y.a;
}
bool cmpb(node x, node y) {
if (x.b == y.b)return x.c < y.c;
return x.b < y.b;
}
signed main()
{
int n, k;
cin >> n >> k;
vector<int>num(n, 0);
vector<node>a, b(n);
for (int i = 0; i < n; ++i) {
cin >> b[i].a >> b[i].b >> b[i].c;
b[i].w = 1;
}
sort(b.begin(), b.end(), cmpa);
int cnt = 0;//捆绑处理完全相同的元素
for (int i = 0; i < n; ++i) {
cnt++;
if (b[i].a != b[i + 1].a || b[i].b != b[i + 1].b || b[i].c != b[i + 1].c) {
a.emplace_back(b[i]);
a.back().w = cnt;
a.back().ans = 0;
cnt = 0;
}
}
n = a.size();
auto cdq = [&](auto self, int l, int r) {
if (l == r)return ;
int mid = (l + r) >> 1;
self(self, l, mid); self(self, mid + 1, r);
sort(a.begin() + l, a.begin() + mid + 1, cmpb);
sort(a.begin() + mid + 1, a.begin() + r + 1, cmpb);
int p1 = l, p2 = mid + 1;
for (; p2 <= r; ++p2) {
while (p1 <= mid && a[p1].b <= a[p2].b) {
upd(a[p1].c, a[p1].w);
++p1;
}
a[p2].ans += ask(a[p2].c);
}
for (--p1; p1 >= l; --p1) {//这里用原路返回清空 tr,节省时间
upd(a[p1].c, -a[p1].w);
}
};
cdq(cdq, 0, n - 1);
for (int i = 0; i < n; ++i) {num[a[i].ans + a[i].w - 1] += a[i].w;}//统计答案为 i 的元素的个数 num[i]
for (auto c : num)cout << c << endl;
}