// // // // // // // // // // // // // //

关于 CDQ分治(复习)

关于 CDQ 分治

前言

之前学过一遍 但是理解不是很到位 过了一段时间之后忘差不多了 回过头来复习 顺便做一下笔记 并不适合初学阅读


这里的只是基础 并不涉及什么优化 dp 之类的东西


偏序

CDQ 的模板题就是一道偏序...

一维偏序

这个我会

直接排序

二维偏序

其实就是逆序对了...

归并或者树状数组都阔以

感觉归并排序就差不多是 CDQ 分治的过程了

三维偏序

模板题

一维排序 按照第一维排序 消去第一维的限制

二维归并 对第二维归并 考虑左边对右边的贡献

三维树状数组 在第二维归并的过程中 在第三维上用树状数组统计贡献

代码

/*
  Source: P3810 【模板】三维偏序(陌上花开)
*/
#include<cstdio>
#include<algorithm>
#define pn putchar('\n')
/*----------------------------------------------------------*/
const int B = 1e5 + 7;
/*----------------------------------------------------------*/
int n, K, cnt, ans[B];
struct node {int a, b, c, cnt, ans;} a[B], b[B];
/*----------------------------------------------------------*/
inline int read() {
	int x = 0, f = 1; char ch = getchar();
	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
	return x * f;
}
void Print(int x) {if(x < 0) putchar('-'), x = -x; if(x > 9) Print(x / 10); putchar(x % 10 ^ 48);}
/*----------------------------------------------------------*/
namespace AT {
	#define lb(x) ((x) & -(x))
	struct node {int val, t;} t[B << 1];
	int Ti;
	void clear() {Ti++;}
	void add(int x, int k) {
		for(int i = x; i <= K; i += lb(i))
		{
			if(t[i].t < Ti) t[i].t = Ti, t[i].val = 0;
			t[i].val += k;
		}
	}
	int sum(int x) {
		int res = 0;
		for(int i = x; i; i -= lb(i))
		{
			if(t[i].t < Ti) t[i].t = Ti, t[i].val = 0;
			res += t[i].val;
		}
		return res;
	}
}
bool cmp1(node x, node y) {return x.a == y.a ? x.b == y.b ? x.c < y.c : x.b < y.b : x.a < y.a;}
bool cmp2(node x, node y) {return x.b == y.b ? x.c < y.c : x.b < y.b;}
void CDQ(int l, int r) {
	if(l == r) return ; int mid = l + r >> 1; CDQ(l, mid); CDQ(mid + 1, r);
	std::sort(a + l, a + mid + 1, cmp2); std::sort(a + mid + 1, a + r + 1, cmp2);
	for(int i = mid + 1, j = l; i <= r; i++)
	{
		while(a[j].b <= a[i].b && j <= mid) AT::add(a[j].c, a[j].cnt), j++;
		a[i].ans += AT::sum(a[i].c);
	}
	AT::clear();
}
void Main() {
	n = read(); K = read();
	for(int i = 1; i ^ n + 1; i++) b[i] = (node){read(), read(), read(), 1};
	std::sort(b + 1, b + 1 + n, cmp1);
	for(int i = 1, num = 1; i ^ n + 1; i++, num++)
		if(b[i].a ^ b[i + 1].a || b[i].b ^ b[i + 1].b || b[i].c ^ b[i + 1].c)
			a[++cnt] = b[i], a[cnt].cnt = num, num = 0;
	CDQ(1, cnt);
	for(int i = 1; i ^ cnt + 1; i++) ans[a[i].ans + a[i].cnt - 1] += a[i].cnt;
	for(int i = 0; i ^ n; i++) Print(ans[i]), pn;
}
/*----------------------------------------------------------*/
signed main() {Main(); return 0;}

可不可以不用树状数组呢? 自然可以

另一种写法: CDQ 套 CDQ

一维排序 按照第一维排序 消去第一维的限制

二维归并 对第二维归并 同时标记 消去第二维的限制

三维归并 结合第二维的标记 归并过程中统计答案

代码

/*
  Source: P3810 【模板】三维偏序(陌上花开)
*/
#include<cstdio>
#include<algorithm>
#define pn putchar('\n')
/*----------------------------------------------------------*/
const int B = 1e5 + 7;
/*----------------------------------------------------------*/
int n, K, ans[B], cnt, d[B];
struct node {int a, b, c, id, cnt, ans; bool flag;} a[B], tmp1[B], tmp2[B];
/*----------------------------------------------------------*/
inline int read() {
	int x = 0, f = 1; char ch = getchar();
	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
	return x * f;
}
void Print(int x) {if(x < 0) putchar('-'), x = -x; if(x > 9) Print(x / 10); putchar(x % 10 ^ 48);}
/*----------------------------------------------------------*/
bool cmp1(node x, node y) {return x.a == y.a ? x.b == y.b ? x.c < y.c : x.b < y.b : x.a < y.a;}
void CDQ2(int l, int r) {
	if(r - l <= 1) return ; int mid = l + r >> 1; CDQ2(l, mid); CDQ2(mid, r);
	int i = l, j = mid, kcnt = l, sum = 0;
	while(i < mid && j < r)
		if(tmp1[i].c <= tmp1[j].c)
		{
			if(tmp1[i].flag == 0) sum++;
			tmp2[kcnt++] = tmp1[i++];
		}
		else
		{
			if(tmp1[j].flag == 1) ans[tmp1[j].id] += sum;
			tmp2[kcnt++] = tmp1[j++];
		}
	while(i < mid) tmp2[kcnt++] = tmp1[i++];
	while(j < r)
	{
		if(tmp1[j].flag == 1) ans[tmp1[j].id] += sum;
		tmp2[kcnt++] = tmp1[j++];
	}
	for(int i = l; i ^ r; i++) tmp1[i] = tmp2[i];
}
void CDQ1(int l, int r) {
	if(r - l <= 1) return ; int mid = l + r >> 1; CDQ1(l, mid); CDQ1(mid, r);
	int i = l, j = mid, kcnt = l;
	while(i < mid && j < r)
		if(a[i].b <= a[j].b) tmp1[kcnt] = a[i++], tmp1[kcnt++].flag = 0;
		else tmp1[kcnt] = a[j++], tmp1[kcnt++].flag = 1;
	while(i < mid) tmp1[kcnt] = a[i++], tmp1[kcnt++].flag = 0;
	while(j < r) tmp1[kcnt] = a[j++], tmp1[kcnt++].flag = 1;
	for(int i = l; i ^ r; i++) a[i] = tmp1[i];
	CDQ2(l, r);
}
void Main() {
	n = read(); K = read();
	for(int i = 1; i ^ n + 1; i++) a[i] = (node){read(), read(), read(), i, 0, 0, 0};
	std::sort(a + 1, a + 1 + n, cmp1);
	for(int i = n - 1; i; i--)
		if(a[i].a == a[i + 1].a && a[i].b == a[i + 1].b && a[i].c == a[i + 1].c)
			ans[a[i].id] = ans[a[i + 1].id] + 1;
	CDQ1(1, n + 1);
	for(int i = 1; i ^ n + 1; i++) d[ans[i]]++;
	for(int i = 0; i ^ n; i++) Print(d[i]), pn;
}
/*----------------------------------------------------------*/
signed main() {Main(); return 0;}

四维偏序

一维排序

二维归并

三维归并

四维树状数组

代码没有写了...

多维偏序

通过 CDQ 一层一层的嵌套来进行降维 在每一层中标记 在最里面一层中统计答案

题目

CDQ 同样可以用来处理一些修改查询的问题 有时可以代替一层数据结构

例题1

给定序列 要求支持单点修改 区间求和

\(1 \leq n, m \leq 5 \times 10^5\)

然后你发现这是树状数组的板子题

时间是第一维 默认有序

序列是第二维

当然你反过来也没人拦着你

然后直接上 CDQ 分治就好了


代码

/*
  Source: P3374 【模板】树状数组 1(CDQ)
*/
#include<cstdio>
#define pn putchar('\n')
/*----------------------------------------------------------*/
const int B = 5e5 + 7;
/*----------------------------------------------------------*/
int n, m, cnt, qcnt, ans[B];
struct node {int x, val, id, opt;} a[B << 2], b[B << 2];
/*----------------------------------------------------------*/
inline int read() {
	int x = 0, f = 1; char ch = getchar();
	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
	return x * f;
}
void Print(int x) {if(x < 0) putchar('-'), x = -x; if(x > 9) Print(x / 10); putchar(x % 10 ^ 48);}
/*----------------------------------------------------------*/
bool check(int i, int j) {return a[i].x == a[j].x ? a[i].opt < a[j].opt : a[i].x < a[j].x;}
void CDQ(int l, int r) {
	if(r - l <= 1) return ; int mid = l + r >> 1; CDQ(l, mid); CDQ(mid, r);
	int i = l, j = mid, kcnt = l, sum = 0;
	while(i < mid && j < r)
		if(check(i, j))
		{
			if(a[i].opt == 1) sum += a[i].val;
			b[kcnt++] = a[i++];
		}
		else
		{
			if(a[j].opt == 2) ans[a[j].id] -= sum;
			else if(a[j].opt == 3) ans[a[j].id] += sum;
			b[kcnt++] = a[j++];
		}
	while(i < mid) b[kcnt++] = a[i++];
	while(j < r)
	{
		if(a[j].opt == 2) ans[a[j].id] -= sum;
		else if(a[j].opt == 3) ans[a[j].id] += sum;
		b[kcnt++] = a[j++];
	}
	for(int k = l; k ^ r; k++) a[k] = b[k];
}
void Main() {
	n = read(); m = read();
	for(int i = 1; i ^ n + 1; i++) a[++cnt] = (node){i, read(), 0, 1};
	for(int i = 1; i ^ m + 1; i++)
		if(read() == 1) a[++cnt] = (node){read(), read(), 0, 1};
		else
		{
			int x = read(), y = read(); ++qcnt;
			a[++cnt] = (node){x - 1, 0, qcnt, 2};
			a[++cnt] = (node){y, 0, qcnt, 3};
		}
	CDQ(1, cnt + 1);
	for(int i = 1; i ^ qcnt + 1; i++) Print(ans[i]), pn;
}
/*----------------------------------------------------------*/
signed main() {Main(); return 0;}

做完这道题之后仿佛发现了新世界的大门 "还能这么玩" 于是愈走愈远

例题2

给定序列 要求支持区间修改 单点查询

\(1 \leq n, m \leq 5 \times 10^5\)

细心的你发现这是树状数组2

一维时间

二维序列

差分一下可以直接做

代码

/*
  Source: P3368 【模板】树状数组 2
*/
#include<cstdio>
#include<cstring>
#define pt putchar(' ')
#define pn putchar('\n')
#define Abs(x) ((x) < 0 ? -(x) : (x))
#define Max(x, y) ((x) > (y) ? (x) : (y))
#define Min(x, y) ((x) < (y) ? (x) : (y))
#define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
/*----------------------------------------------------------*/
const int A = 1e4 + 7;
const int B = 5e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*----------------------------------------------------------*/
inline void File() {
	freopen(".in", "r", stdin);
	freopen(".out", "w", stdout);
}
/*----------------------------------------------------------*/
int n, m, c[B], cnt, qcnt, ans[B << 2];
struct node {int x, val, id, opt;} a[B << 2], b[B << 2];
/*----------------------------------------------------------*/
inline int read() {
	int x = 0, f = 1; char ch = getchar();
	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
	return x * f;
}
void Print(int x) {if(x < 0) putchar('-'), x = -x; if(x > 9) Print(x / 10); putchar(x % 10 ^ 48);}
/*----------------------------------------------------------*/
bool check(int i, int j) {return a[i].x == a[j].x ? a[i].opt < a[j].opt : a[i].x < a[j].x;}
void CDQ(int l, int r) {
	if(r - l <= 1) return ; int mid = l + r >> 1; CDQ(l, mid); CDQ(mid, r);
	int i = l, j = mid, kcnt = l, sum = 0;
	while(i < mid && j < r)
		if(check(i, j))
		{
			if(a[i].opt == 1) sum += a[i].val;
			b[kcnt++] = a[i++];
		}
		else
		{
			if(a[j].opt == 2) ans[a[j].id] += sum;
			b[kcnt++] = a[j++];
		}
	while(i < mid) b[kcnt++] = a[i++];
	while(j < r)
	{
		if(a[j].opt == 2) ans[a[j].id] += sum;
		b[kcnt++] = a[j++];
	}
	for(int k = l; k ^ r; k++) a[k] = b[k];
}
void Main() {
	n = read(); m = read(); int last = 0;
	for(int i = 1, x; i ^ n + 1; i++) x = read(), c[i] = x - last, last = x;
	for(int i = 1; i ^ n + 1; i++) a[++cnt] = (node){i, c[i], 0, 1};
	for(int i = 1; i ^ m + 1; i++)
		if(read() == 1)
		{
			int x = read(), y = read(), z = read();
			a[++cnt] = (node){x, z, 0, 1}; a[++cnt] = (node){y + 1, -z, 0, 1};
		}
		else a[++cnt] = (node){read(), 0, ++qcnt, 2};
	CDQ(1, cnt + 1);
	for(int i = 1; i ^ qcnt + 1; i++) Print(ans[i]), pn;
}
/*----------------------------------------------------------*/
signed main() {Main(); return 0;}

例题3

给定二维平面 要求支持单点修改 矩阵查询

\(1 \leq n, m \leq 10^6\)

时间作为第一维

\(x\) 轴为第二维

\(y\) 轴为第三维

时间作为第一维的原因是默认有序 不需要再排一遍 第二维归并 第三维树状数组维护即可

查询的时候将一次查询差分成四个即可

代码

/*
  Source: P4390 [BOI2007]Mokia 摩基亚
*/
#include<cstdio>
#include<cstring>
#define pt putchar(' ')
#define pn putchar('\n')
#define Abs(x) ((x) < 0 ? -(x) : (x))
#define Max(x, y) ((x) > (y) ? (x) : (y))
#define Min(x, y) ((x) < (y) ? (x) : (y))
#define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
/*----------------------------------------------------------*/
const int A = 1e4 + 7;
const int B = 2e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*----------------------------------------------------------*/
inline void File() {
	freopen(".in", "r", stdin);
	freopen(".out", "w", stdout);
}
/*----------------------------------------------------------*/
int w, cnt, ans[B], qcnt;
struct node {int x, y, val, id, opt;} a[B], b[B];
/*----------------------------------------------------------*/
inline int read() {
	int x = 0, f = 1; char ch = getchar();
	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
	return x * f;
}
void Print(int x) {if(x < 0) putchar('-'), x = -x; if(x > 9) Print(x / 10); putchar(x % 10 ^ 48);}
/*----------------------------------------------------------*/
namespace TA {
	#define lb(x) ((x) & -(x))
	struct node {int t, val;} t[C << 1]; int Ti;
	void add(int x, int k) {
		for(int i = x; i <= w; i += lb(i))
		{
			if(t[i].t < Ti) t[i].t = Ti, t[i].val = 0;
			t[i].val += k;
		}
	}
	int sum(int x) {
		int res = 0;
		for(int i = x; i; i -= lb(i))
		{
			if(t[i].t < Ti) t[i].t = Ti, t[i].val = 0;
			res += t[i].val;
		}
		return res;
	}
}
void CDQ(int l, int r) {
	if(r - l <= 1) return ; int mid = l + r >> 1; CDQ(l, mid); CDQ(mid, r);
	int i = l, j = mid, kcnt = l;
	while(i < mid && j < r)
		if(a[i].x <= a[j].x)
		{
			if(a[i].opt == 1) TA::add(a[i].y, a[i].val);
			b[kcnt++] = a[i++];
		}
		else
		{
			if(a[j].opt == 2) ans[a[j].id] += TA::sum(a[j].y) * a[j].val;
			b[kcnt++] = a[j++];
		}
	while(i < mid) b[kcnt++] = a[i++];
	while(j < r)
	{
		if(a[j].opt == 2) ans[a[j].id] += TA::sum(a[j].y) * a[j].val;
		b[kcnt++] = a[j++];
	}
	for(int k = l; k ^ r; k++) a[k] = b[k];
	TA::Ti++;
}
void Main() {
	read(); w = read();
	while(1)
	{
		int opt = read(); if(opt == 3) break ;
		if(opt == 1) a[++cnt] = (node){read(), read(), read(), 0, 1};
		else
		{
			int x1 = read(), y1 = read(), x2 = read(), y2 = read(); ++qcnt;
			a[++cnt] = (node){x1 - 1, y1 - 1, 1, qcnt, 2};
			a[++cnt] = (node){x1 - 1, y2, -1, qcnt, 2};
			a[++cnt] = (node){x2, y1 - 1, -1, qcnt, 2};
			a[++cnt] = (node){x2, y2, 1, qcnt, 2};
		}
	}
	CDQ(1, cnt + 1);
	for(int i = 1; i ^ qcnt + 1; i++) Print(ans[i]), pn;
}
/*----------------------------------------------------------*/
signed main() {Main(); return 0;}

例题4

给定二维平面 要求支持矩阵修改 单点查询

\(1 \leq n, m \leq 10^6\)

跟上面一样的

一维时间

\(x, y\) 分别作为第二维和第三维

这次是将修改操作差分成四个 就可以直接做了


放代码之前说一下题外话

上午的时候 zxsoul 搞出一个题目 很开森的跑去写了个 CDQ 然后被卡超时 收获卑微的八十分

然后就看到 Ariel 写线段树也被卡了 同样八十分

md 什么出题人会 sb 到卡线段树啊

于是找 zxsoul 大佬把题目要过来并略作修改 包括 略改时限 略改空间 略改数据 略改题目... 于是就有了这个东西

至于为什么这番话会出现在这里 就不得而知了...

绝对不是因为我被卡了才干这种事的 绝对不是

不过顺手把数据结构卡掉的感觉真爽


例题4 的代码

/*
  Source: U175404
*/
#include<cstdio>
#define pn putchar('\n')
#define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
/*----------------------------------------------------------*/
const int C = 1e6 + 7;
/*----------------------------------------------------------*/
int n, m, cnt, qcnt, ans[C];
struct node {int x, y, val, id, opt;} a[C << 2], b[C << 2];
/*----------------------------------------------------------*/
inline int read() {
	int x = 0, f = 1; char ch = getchar();
	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
	return x * f;
}
void Print(int x) {if(x < 0) putchar('-'), x = -x; if(x > 9) Print(x / 10); putchar(x % 10 ^ 48);}
/*----------------------------------------------------------*/
namespace TA {
	#define lb(x) ((x) & -(x))
	struct node {int t, val;} t[C]; int Ti;
	void add(int x, int k) {
		for(int i = x; i <= n; i += lb(i))
		{
			if(t[i].t < Ti) t[i].t = Ti, t[i].val = 0;
			t[i].val += k;
		}
	}
	int sum(int x) {
		int res = 0;
		for(int i = x; i; i -= lb(i))
		{
			if(t[i].t < Ti) t[i].t = Ti, t[i].val = 0;
			res += t[i].val;
		}
		return res;
	}
}
void solve(int l, int r) {
	if(r - l <= 1) return ; int mid = l + r >> 1; solve(l, mid); solve(mid, r);
	int i = l, j = mid, kcnt = l;
	while(i < mid && j < r)
		if(a[i].x <= a[j].x)
		{
			if(a[i].opt == 1) TA::add(a[i].y, a[i].val);
			b[kcnt++] = a[i++];
		}
		else
		{
			if(a[j].opt == 2) ans[a[j].id] += TA::sum(a[j].y);
			b[kcnt++] = a[j++];
		}
	while(i < mid) b[kcnt++] = a[i++];
	while(j < r)
	{
		if(a[j].opt == 2) ans[a[j].id] += TA::sum(a[j].y);
		b[kcnt++] = a[j++];
	}
	for(int k = l; k ^ r; k++) a[k] = b[k]; TA::Ti++;
}
void Main() {
	n = read(); m = read();
	for(int i = 1; i ^ m + 1; i++)
		if(read() == 1)
		{
			int x1 = read(), y1 = read(), x2 = read(), y2 = read(), val = read();
			if(x1 > x2) Swap(x1, x2); if(y1 > y2) Swap(y1, y2);
			a[++cnt] = (node){x1, y1, val, 0, 1};
			a[++cnt] = (node){x1, y2 + 1, -val, 0, 1};
			a[++cnt] = (node){x2 + 1, y1, -val, 0, 1};
			a[++cnt] = (node){x2 + 1, y2 + 1, val, 0, 1};
		}
		else a[++cnt] = (node){read(), read(), 0, ++qcnt, 2};
	solve(1, cnt + 1);
	for(int i = 1; i ^ qcnt + 1; i++) Print(ans[i]), pn;
}
/*----------------------------------------------------------*/
signed main() {Main(); return 0;}
posted @ 2021-08-21 18:04  Blank_space  阅读(74)  评论(0编辑  收藏  举报
// // // // // // //