合辑: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\n", 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\n", 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\n", real_ans[i]);
return 0;
}