首页 > 其他分享 >浅谈cdq分治

浅谈cdq分治

时间:2022-10-27 20:34:09浏览次数:130  
标签:浅谈 int text 分治 leq cdq 区间 define

咕了很久的 \(\text{cdq}\) 终于开始学了。

中间翻了很多博客,最后是看这一篇看懂的。讲的不算详细,但是要点基本都有。

\(三维偏序问题\)

就比如臭名昭著大名鼎鼎的陌上花开

设 \(f(i)\) 为 对于 \(\forall j \not= i\),满足 \(a_i \leq a_j, b_i \leq b_j, c_i \leq c_j\) 的 \(j\) 的数量。本题要求的是 \(\forall d \in [0, n), f(i) = d\) 的数量。

cdq解决此问题的思路十分巧妙,也十分好理解。

  • 首先按照第一维 \(a_i\) 排序,这样就满足了 \(a_i\) 的单调性。

  • 去重。将 \(a_i, b_i, c_i\) 相同的给缩成一个,记录到 \(cnt_i\) 里表示有多少个和 \(i\) 相同的。

  • 分治递归子区间 \([l, mid]\) 和 \([mid+1, r]\),将两个指针 \(i, j\) 分别指向 \(l, mid+1\),然后开始扫这两个区间。

  • 由于计算的是左区间对右区间产生的影响,所以扫右区间,同时将左区间满足 \(b_i \leq b_j\) 的点加入权值树状数组(为了求前缀和),其中 \(c_i\) 作为下标加入,加入的值即为 \(cnt_i\)。

  • 对于每个右端点,查询比他的 \(c_j\) 小的数量,将其扔进这个点的答案数组即可。(注意此时的 \(j\) 是 \(f(i)\) 中的 \(i\))

code
#include <iostream>
#include <algorithm>
#define GMY (520&1314)
#define char_phi int
#define re register int
#define FBI_OPENTHEDOOR(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#define Endl cout << '\n'
#define _ ' '
#define Dl cerr << '\n'
#define DMARK cerr << "###"
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define N 100005
#define MXS 200005
using namespace std;
inline void Fastio_setup(){ ios::sync_with_stdio(false); cin.tie(NULL), cout.tie(NULL); }

/*
	学cdq来了
*/

int n, mxs, final_ans, tmpn;
int ans[N];

struct Element {int x, y, z, cnt, answer;};
struct TreeArray{
	#define lowbit(x) ((x) & (-(x)))
	int C[MXS];
	inline void Update(int k, int w){
		while (k <= mxs)
			C[k] += w, k += lowbit(k);// , cerr << k << '\n';// , DMARK;
	}
	inline int Query(int k){
		int res(0);
		while (k != 0)
			res += C[k], k -= lowbit(k);
		return res;
	}
};

struct Element arr[N], a[N];
struct TreeArray TA;

inline bool compx(Element A, Element B){ return ( (A.x == B.x) ? ( (A.y == B.y) ? (A.z < B.z) : (A.y < B.y) ) : (A.x < B.x) ); }
inline bool compy(Element A, Element B){ return ( (A.y == B.y) ? ( (A.x == B.x) ? (A.z < B.z) : (A.x < B.x) ) : (A.y < B.y) ); }

#define mid ((L+R)>>1)

void cdq(int L, int R){
	// cerr << "cdq: " << L << _ << R << '\n';
	if (L == R)
		return ;
	cdq(L, mid); cdq(mid+1, R);
	sort(a+L, a+mid+1, compy); sort(a+mid+1, a+R+1, compy);// 我才想到第二个传的参应当是右区间-1。。
	
	int l = L, r = mid+1;
	while (r <= R){
		while (l <= mid and a[l].y <= a[r].y)
			TA.Update(a[l].z, a[l].cnt), l ++;
		a[r].answer += TA.Query(a[r].z);
		r ++;
	}
	
	for (re i = L ; i <= l-1 ; ++ i)
		TA.Update(a[i].z, -a[i].cnt);
	
	/*int l = L, r = mid+1;
	while (l <= mid){
		while (r <= R and a[l].y <= a[r].y)
			TA.Update(a[r].z, a[r].cnt), r ++;
		a[l].answer += TA.Query(a[l].z);
		l ++;
	}
	
	// 有单调性,所以最后再清空是正确的
	
	for (re i = mid+1 ; i <= r-1 ; ++ i)// 注意终点不是R,因为可能r到不了R
		TA.Update(a[i].z, -a[i].cnt);*/
	
}

#undef mid

inline void work(){
	cin >> tmpn >> mxs;
	for (re i = 1, x, y, z ; i <= tmpn ; ++ i)
		{cin >> x >> y >> z; arr[i] = (Element){x, y, z, 0};}
	
	sort(arr+1, arr+tmpn+1, compx);
	
	for (re i = 1, num(0) ; i <= tmpn ; ++ i){
		num ++;
		if (arr[i].x != arr[i+1].x or arr[i].y != arr[i+1].y or arr[i].z != arr[i+1].z)
			a[++ n] = arr[i], a[n].cnt = num, num = 0;
	}
	
	cdq(1, n);
	
	// DMARK;
	
	for (re i = 1 ; i <= n ; ++ i)
		ans[a[i].answer+a[i].cnt-1] += a[i].cnt;
	
	/*for (re i = 0 ; i <= tmpn-1 ; ++ i)
		cerr << ans[i] << _;
	Dl;*/
	
	for (re i = 0 ; i <= tmpn-1 ; ++ i)
		cout << ans[i] << '\n';
}
#undef int
// #define IXINGMY
char_phi main(){
	#ifdef IXINGMY
		FBI_OPENTHEDOOR(a, a);
	#endif
	Fastio_setup();
	work();
	return GMY;
}

\(\text{sto}\) 权值树状数组 \(\text{orz}\)

标签:浅谈,int,text,分治,leq,cdq,区间,define
From: https://www.cnblogs.com/charphi/p/16793526.html

相关文章