CDQ 分治学习笔记

Working…

本文由 Pretharp 编写,转载需注明出处,禁止用于任何形式的商业用途。

1 引入

1.1 算法相关

CDQ 分治准确而言不是某种算法,而是一种思想。分治,顾名思义,就是面对大问题时,将问题划分为若干小问题,再逐一 忽略 解决。值得一提的是,CDQ 分治通常是离线算法,接下来,我们将以例题讲解 CDQ 分治。

2 例题

2.1 例题 1

[洛谷 P3810] 【模板】三维偏序(陌上花开)

题目大意:

给定 \(n\) 个元素,每个元素有三个属性 \(a_i,b_i\)\(c_i\)。令 \(f(i)\) 表示满足 \(a_j \le a_i\)\(b_j \le bi\)\(c_j \le c_i\)\(j \neq i\)\(j\) 的个数。求对于 \(d \in [0,n)\) 中,\(f(i)=d\)\(i\) 的个数。

分析:

这是一道三维偏序题,我们用 CDQ 分治的思想解决。首先,我们将所有元素以 \(a_i\) 为第一关键字升序排序,那么我们对于区间 \([l,r]\),显然,区间内 \([l,mid]\) 的元素将有可能对 \((mid,r]\) 的元素的答案做出贡献,因为对于 \(i \in [l,mid],j \in (mid,r]\) 必然有 \(a_i \le a_j\)\(mid\) 是区间中点)。接下来,我们可以将 \([l,mid]\)\((r,mid]\) 内的元素分别以 \(b_i\) 为第一关键字排序,此时答案已然变为二维偏序问题,用树状数组维护即可求得答案。

参考代码(部分):

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5, M = 2e5 + 5;
int tn, n, k, buc[M];
struct Ele {
	int x, y, z, ans, same;
} tmp[N], a[N];
bool cmpWithX(const Ele &x, const Ele &y) {
	return (x.x == y.x ? (x.y == y.y ? x.z < y.z : x.y < y.y) : x.x < y.x);
}
bool cmpWithY(const Ele &x, const Ele &y) {
	return (x.y == y.y ? x.z < y.z : x.y < y.y);
}
namespace FenwickTree {...}
ttFT::ttFT fwt; // 这两行代码就是定义了一个树状数组。
void CDQ(int l, int r) {
	if(l >= r) {
		return;
	}
	int mid = l + r >> 1;
	CDQ(l, mid), CDQ(mid + 1, r);
	sort(a + l, a + 1 + mid, cmpWithY), 
	sort(a + 1 + mid, a + 1 + r, cmpWithY);
	int i = mid + 1, j = l;
	for(; i <= r; i ++) {
		while(j <= mid && a[j].y <= a[i].y) {
			fwt.modify(a[j].z, a[j].same);
			j ++;
		}
		a[i].ans += fwt.query(a[i].z);
	}
	for(int o = l; o < j; o ++) {
		fwt.modify(a[o].z, -a[o].same);
	}
}
signed main() {
	cin >> tn >> k;
	for(int i = 1; i <= tn; i ++) {
		cin >> tmp[i].x >> tmp[i].y >> tmp[i].z;
	}
	sort(tmp + 1, tmp + 1 + tn, cmpWithX);
	for(int i = 1, cnt = 0; i <= tn; i ++) {
		cnt ++;
		if(
			tmp[i].x == tmp[i + 1].x
			&& tmp[i].y == tmp[i + 1].y
			&& tmp[i].z == tmp[i + 1].z
		) {
			continue;
		}
		a[++ n] = tmp[i], a[n].same = cnt, cnt = 0;
	}
	CDQ(1, n);
	for(int i = 1; i <= n; i ++) {
		buc[a[i].ans + a[i].same - 1] += a[i].same;
	}
	for(int i = 0; i < tn; i ++) {
		cout << buc[i] << endl;
	}
	return 0;
}

[CQOI2011] 动态逆序对

题目大意:

给定一个 \(1 \sim n\) 的排列,接下来按照顺序依次删除 \(m\) 个数,问每次删除之前逆序对的数量。

分析:

其实与上一题很相似。考虑对于一个点有那些点会与其成为逆序对,不难发现对于点 \(i\)\(j\) 会对 \(i\) 产生贡献有:

  • \(j\) 的删除时间比 \(i\) 晚。
  • \(j\) 的位置比 \(i\) 更靠前且权值比 \(i\) 更大,或者 \(j\) 的位置更靠后且权值更小。

形式化的, \(time_j>time_i\)\(pos_j<pos_i\)\(val_j>val_i\),或 \(time_j>time_i\)\(pos_j>pos_i\)\(val_j<val_i\)\(pos\) 是位置,\(val\) 是权值,\(time\) 是删除时间)。至此,这道题已经变成三维偏序。

代码略。

posted @ 2023-10-05 21:23  Pretharp  阅读(108)  评论(2)    收藏  举报