合辑:CDQ分治

听考拉的主人说CDQ分治是她考NOI的时候现yy出来的,太强了Orz

CDQ分治主要是一种思想,用来处理离线的问题。

学长说可以优化DP,但我觉得适用面还蛮窄的,主要还是处理序列问题,最主要的应该就是偏序。

CDQ分治的主要思想就是用前一个子问题解决后一个子问题,比如你把一个询问区间二分,左边的区间维护答案,右边的区间查询左边区间维护的答案。至于右边的维护操作和左边的操作?交给下一层去解决了。

这就限制了CDQ分治的适用范围,需要修改操作对询问的贡献是独立的且互不影响。

Problem A:陌上花开

维护三维偏序。

树套树裸题,切了,走了(误

树套树又臭又长能不写就不写,CDQ好写好调,来写CDQ

先想二维偏序我们是怎么解决的:先按照第一维排个序,在第二维统计答案

三维偏序多了一维,还是在最外层第三维统计答案,第二维套个CDQ。

具体一点,先按a排个序,顺便去重。然后跑CDQ,通过跑归并把b排序,同时用数据结构维护c的偏序,这里是树状数组,同时之前的归并保证了b的有序。

#include <bits/stdc++.h>

const int N = 200000 + 233;
int n, k, ans[N];
struct Flower {int a, b, c, cnt, f;} a[N], b[N];

bool cmp(const Flower &x, const Flower &y) {
	return (x.a == y.a && x.b == y.b) ? x.c < y.c : (x.a == y.a) ? x.b < y.b : x.a < y.a;
}

struct BIT {
	int t[N];
	BIT() {memset(t, 0, sizeof(t));}
	inline void Add(int x, int v) {
		while (x <= k) t[x] += v, x += x & -x;
	}
	inline int Ask(int x) {
		int ret = 0;
		while (x) ret += t[x], x -= x & -x;
		return ret;
	}
} bit;

void CDQ(int l, int r) {
	if (l == r) return;
	int mid = (l + r) >> 1, pl = l, pr = mid + 1, p = l;
	CDQ(l, mid), CDQ(mid + 1, r);
	while (pl <= mid || pr <= r) {
		if ((a[pl].b <= a[pr].b && pl <= mid) || (pr > r)) bit.Add(a[pl].c, a[pl].cnt), b[p++] = a[pl++];
		else a[pr].f += bit.Ask(a[pr].c), b[p++] = a[pr++];
	}
	for (int i = l; i <= mid; i++) bit.Add(a[i].c, -a[i].cnt);
	for (int i = l; i <= r; i++) a[i] = b[i];
}

signed main() {
	scanf("%d%d", &n, &k);
	for (int i = 1; i <= n; i++)
		scanf("%d%d%d", &a[i].a, &a[i].b, &a[i].c), a[i].cnt = 1;
	std::sort(a + 1, a + 1 + n, cmp);
	int p = 1;
	for (int i = 2; i <= n; i++) {
		if (a[i].a == a[p].a && a[i].b == a[p].b && a[i].c == a[p].c) a[p].cnt++;
		else a[++p] = a[i];
	}
	CDQ(1, p);
	for (int i = 1; i <= p; i++) ans[a[i].f + a[i].cnt - 1] += a[i].cnt;
	for (int i = 0; i < n; i++) printf("%d
", ans[i]);
	return 0;
}

Problem B & C: Mokia & 简单题

双倍的快乐

查询矩阵权值容易想到二维前缀和,把右上角和左下角的加上,把左上角和右下角的减掉。查询操作就被拆成了4个。

关于修改操作,一个修改对查询有贡献,当且仅当它的xyt都不大于查询,这就又成了三维偏序,继续CDQ。

老生长谈的问题,y1之类的变量名是会撞的,你必CE。解决方法两个,一个是你换变量名,或者如果你像我一样固执,可以开个namespace规避打击。

简单题在Luogu是强制在线的,双倍经验请谨慎,你CDQ必死(

#include <bits/stdc++.h>

namespace Gekoo {
	const int N = 2000000 + 233;
	int s, w, qwq, qaq, ans[N];

	struct Command {
		int opt, x, y, a, p;
	} cmd[N], tmp[N];

	struct Bit {
		int t[N];
		inline void Add(int p, int d) {
			for (; p <= w; p += p & -p) t[p] += d;
		}
		inline int Ask(int p) {
			int ret = 0;
			for (; p; p -= p & -p) ret += t[p];
			return ret;
		} 
	} BIT;

	inline int R() {
		int a = 0; char c = getchar();
		while (!isdigit(c)) c = getchar();
		while (isdigit(c)) a = a * 10 + c - '0', c = getchar();
		return a;
	}

	void CDQ(int l, int r) {
		if (l == r) return;
		int mid = (l + r) >> 1, pl = l, pr = mid + 1, p = l;
		CDQ(l, mid), CDQ(mid + 1, r);
		while (pl <= mid || pr <= r) {
			if ((cmd[pl].x <= cmd[pr].x && pl <= mid) || pr > r) {
				if (cmd[pl].opt == 1) BIT.Add(cmd[pl].y, cmd[pl].a);
				tmp[p++] = cmd[pl++];
			} else {
				if (cmd[pr].opt == 2) {
					if (cmd[pr].p > 0) ans[cmd[pr].p] += BIT.Ask(cmd[pr].y);
					else ans[-cmd[pr].p] -= BIT.Ask(cmd[pr].y);
				}
				tmp[p++] = cmd[pr++];
			}
		}
		for (int i = l; i <= mid; i++) 
			if (cmd[i].opt == 1) BIT.Add(cmd[i].y, -cmd[i].a);
		for (int i = l; i <= r; i++) cmd[i] = tmp[i];
	}

	signed QAQ() {
		s = R(), w = R();
		while (19260817 < 20030325) {
			int opt = R();
			if (opt == 3) break;
			if (opt == 1) cmd[++qwq].opt = 1, cmd[qwq].x = R(), cmd[qwq].y = R(), cmd[qwq].a = R();
			if (opt == 2) {
				int x1 = R(), y1 = R(), x2 = R(), y2 = R();
				ans[++qaq] = (x2 - x1 + 1) * (y2 - y1 + 1) * s;
				cmd[++qwq].opt = 2, cmd[qwq].p = qaq, cmd[qwq].x = x1 - 1, cmd[qwq].y = y1 - 1;
				cmd[++qwq].opt = 2, cmd[qwq].p = qaq, cmd[qwq].x = x2, cmd[qwq].y = y2;
				cmd[++qwq].opt = 2, cmd[qwq].p = -qaq, cmd[qwq].x = x1 - 1, cmd[qwq].y = y2;
				cmd[++qwq].opt = 2, cmd[qwq].p = -qaq, cmd[qwq].x = x2, cmd[qwq].y = y1 - 1;
			}
		}
		CDQ(1, qwq);
		for (int i = 1; i <= qaq; i++)
			printf("%d
", ans[i]);
		return 0;
	}
}

signed main() {
	return Gekoo::QAQ();
}

Problem C: 天使玩偶

绝对值维护不能,考虑通过坐标的整体移动把坐标全变成正的,然后每个点只与在他左上方的点算距离。式子就变成 (dis = (x_1 + y_1) - (x_2 + y_2)).

为了让每个点都可以被算到,把所有点按90度转四次,这样每个点都有机会到计算点的左上角。

注意细节:树状数组和统计数组记得赋初值!不然必死。。

还有,我写的时候sort写在统计后头了,调半天调不出来。。。这是错误的,我们要保证统计的时候点按时间排好序,否则对不上,会错位。

这题在Luogu有巨卡常数的数据,这个算法显然是可以继续优化的,但我选择了吸氧(逃

#include <bits/stdc++.h>

const int N = 1000000 + 233, INF = 0x3f3f3f3f;
int n, m, ans[N], real_ans[N], mx_x, mx_y;
struct Doll {
	int x, y, t, id;
	friend bool operator <(Doll x, Doll y) {
		return x.id < y.id;
	}
} d[N], tmp[N];

struct Bit {
	int t[N];
	inline void Add(int x, int d) {
		for (; x <= mx_y; x += x & -x) t[x] = std::max(t[x], d);
	}
	inline int Ask(int x) {
		int ret = -INF;
		for (; x; x -= x & -x) ret = std::max(ret, t[x]);
		return ret;
	}
	inline void Clear(int x) {
		for (; x <= mx_y; x += x & -x) t[x] = -INF;
	}
} BIT;

inline int R() {
	int a = 0; char c = getchar();
	while (!isdigit(c)) c = getchar();
	while (isdigit(c)) a = a * 10 + c - '0', c = getchar();
	return a;
}

void CDQ(int l, int r) {
	if (l == r) return;
	int mid = (l + r) >> 1, pl = l, pr = mid + 1, p = l;
	CDQ(l, mid), CDQ(mid + 1, r);
	while (pl <= mid || pr <= r) {
		if ((pl <= mid && d[pl].x <= d[pr].x) || pr > r) {
			if (d[pl].t == 1) BIT.Add(d[pl].y, d[pl].x + d[pl].y);
			tmp[p++] = d[pl++];
		}
		else {
			if (d[pr].t == 2) ans[d[pr].id] = std::max(ans[d[pr].id], BIT.Ask(d[pr].y));
			tmp[p++] = d[pr++];
		}
	}
	for (int i = l; i <= mid; i++) BIT.Clear(d[i].y);
	for (int i = l; i <= r; i++) d[i] = tmp[i];
}

signed main() {
	n = R(), m = R();
	for (int i = 1; i <= n; i++)
		d[i].x = R() + 1, d[i].y = R() + 1, d[i].t = 1, d[i].id = i, mx_x = std::max(d[i].x, mx_x), mx_y = std::max(d[i].y, mx_y);
	for (int i = 1; i <= m; i++)
		d[i + n].t = R(), d[i + n].id = i + n, mx_x = std::max(d[i + n].x = R() + 1, mx_x), mx_y = std::max(d[i + n].y = R() + 1, mx_y);
	for (int i = 1; i <= n + m; i++)
		real_ans[i] = INF, ans[i] = -INF;
	for (int i = 0; i <= mx_x + mx_y + 2; i++) BIT.t[i] = -INF;
	mx_x++, mx_y++, CDQ(1, n + m);

	std::sort(d + 1, d + 1 + n + m);
	for (int i = 1; i <= n + m; i++)
		real_ans[i] = std::min(real_ans[i], d[i].x + d[i].y - ans[i]), d[i].x = mx_x - d[i].x, ans[i] = -INF;
	CDQ(1, n + m);

	std::sort(d + 1, d + 1 + n + m);
	for (int i = 1; i <= n + m; i++)
		real_ans[i] = std::min(real_ans[i], d[i].x + d[i].y - ans[i]), d[i].y = mx_y - d[i].y, ans[i] = -INF;
	CDQ(1, n + m);

	std::sort(d + 1, d + 1 + n + m);
	for (int i = 1; i <= n + m; i++)
		real_ans[i] = std::min(real_ans[i], d[i].x + d[i].y - ans[i]), d[i].x = mx_x - d[i].x, ans[i] = -INF; 
	CDQ(1, n + m);

	std::sort(d + 1, d + 1 + n + m);
	for (int i = 1; i <= n + m; i++)
		real_ans[i] = std::min(real_ans[i], d[i].x + d[i].y - ans[i]);
	for (int i = 1; i <= n + m; i++)
		if (d[i].t == 2)
			printf("%d
", real_ans[i]);
	return 0;
}
原文地址:https://www.cnblogs.com/gekoo/p/11249445.html