以前做过的许多题目,都是在线算法,也就是对与一系列的询问,利用某种数据结构逐个求出答案。
而今天学了一种离线算法——CDQ分治,是将全部的询问放在一起,利用分治一同处理。
CDQ分治,由2008年国际信息学奥林匹克竞赛(IOI)金牌女选手陈丹琦在国家集训队中引入而得名,为算法竞赛界中的一个广泛称呼。
CDQ分治有两种,分别是基于时间的分治和基于值域的整体分治。
下面看就开始吧。
1.基于时间的分治
BZOJ2716–天使玩具
【题意】
Ayu在七年前曾经收到过一个天使玩偶,当时她把它当做时间囊埋在了地下。
而七年后的今天,Ayu却忘了她把天使玩偶埋在了哪里,所以她决定仅凭一点模糊的记忆来寻找它。
我们把Ayu生活的小镇看做一个二维平面直角坐标系,而Ayu会不定时的记起可能在某个点(x,y)埋下了天使玩偶。
或者Ayu会询问你,假如她在(x,y),那么她离最近的天使玩偶可能埋下的地方有多远。
因为Ayu只会沿着平行坐标轴的方向来行动,所以在这个问题里我们定义两个点之间的距离为曼哈顿距离:
dist(A,B)=|Ax−Bx|+|Ay−By|
其中Ax,Ay表示点A的横坐标,其余类似。
【输入格式】
第一行包含两个整数n和m,在刚开始时,Ayu已经知道有n个点可能埋着天使玩偶,接下来Ayu要进行m次操作。
接下来n行,每行两个非负整数xi,yi,表示初始n个点的坐标。
再接下来m行,每行三个非负整数 t,x,y 。
如果t=1,表示Ayu又回忆起了一个可能埋着玩偶的点(x,y)。
如果t=2,表示Ayu询问如果她在坐标(x,y),那么在已经回忆出的点里,离她最近的那个点有多远。
【输出格式】
对于每个t=2的询问,在单独的一行内输出该询问的结果。
【数据范围】
,坐标范围为。
【输入样例】
2 3
1 1
2 3
2 1 2
1 3 3
2 4 2
【输出样例】
1
2
我们一般先解决该问题的简化版——假设没有t=1的操作。这时,平面上有n个点,然后询问与最近的点有多远,答案为:
min(abs(x-xi)+abs(y-yi))
于是我们考虑将绝对值拆开,分为左上、右上、左下、右下四个位置
对于左下方为例,此时要求的式子变为:
min(x-xi+y-yi)
化简得到:
(x+y)-max(xi+yi)
然后就好办了,我们先将每一个坐标按x为第一关键字,y为第二关键字排序。然后用树状数组来维护[0,y]从左到右不断维护就可以了。
对于其他三个方向,做法也是如此。
然后我们加入修改操作。我们先将插入和询问合并在一起,然后进行分治。为什么要分治呢,举个例子:
在这个图中,我们假设橙色是查询,绿色是修改
然后我们就对其他进行分治
先是把它分成1-4和5-8,用树状数组记录1-4中的修改,给5-8的询问加上这些修改。
然后再分(拿1-4为例子),分成1-2和3-4,用树状数组记录,给3-4中的询问加上
后面同理。
总的来讲,就是把当前的分成两份,用树状数组记录1-mid里面的修改操作,然后给mid+1-r里面的询问操作加上这些结果
因此,我们刚好使每个询问都经过了全部的操作,时间复杂度
参考代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
using namespace std;
const int u = 1000010;
struct rec { int x, y, z; };
rec a[u]; // 原始问题的操作序列(长度为n+m)
rec b[u]; // 静态问题的坐标(按横坐标排序)及其在a中的下标
int c[u], tot; // 树状数组、坐标的最大范围
int ans[u], n, m, t;
bool operator <(const rec &a, const rec &b) {
return a.x < b.x || a.x == b.x && a.y < b.y;
}
int ask(int x) {
int y = -(1 << 30);
for (; x; x -= x & -x) y = max(y, c[x]);
return y;
}
void insert(int x, int y) {
for (; x < tot; x += x & -x) c[x] = max(c[x], y);
}
// 求解简化版问题,需要考虑b[st~ed]的坐标,根据4个方向的不同,
// 横坐标顺序为de(±1),树状数组维护的信息用系数dx,dy(±1)指定
void calc(int st, int ed, int de, int dx, int dy) {
for (int i = st; i != ed; i += de) {
int y = dy == 1 ? b[i].y : tot - b[i].y;
int temp = dx*b[i].x + dy*b[i].y;
if (a[b[i].z].z == 1) insert(y, temp);
else ans[b[i].z] = min(ans[b[i].z], abs(temp - ask(y)));
}
for (int i = st; i != ed; i += de) { // 还原树状数组
int y = dy == 1 ? b[i].y : tot - b[i].y;
if (a[b[i].z].z == 1) // 撤销修改
for (int j = y; j < tot; j += j & -j) c[j] = -(1 << 30);
}
}
void cdqdiv(int l, int r) {
int mid = (l + r) >> 1;
if (l < mid) cdqdiv(l, mid);
if (mid + 1 < r) cdqdiv(mid + 1, r);
t = 0;
for (int i = l; i <= r; i++)
if (i <= mid && a[i].z == 1 || i > mid && a[i].z == 2)
b[++t] = a[i], b[t].z = i;
// 此处排序可以优化掉(放在外边并适当修改写法)
sort(b + 1, b + t + 1);
calc(1, t + 1, 1, 1, 1), calc(t, 0, -1, -1, -1);
calc(1, t + 1, 1, 1, -1), calc(t, 0, -1, -1, 1);
}
int main() {
cin >> n >> m; m += n;
for (int i = 1; i <= n; i++)
scanf("%d%d", &a[i].x, &a[i].y), a[i].y++, a[i].z = 1;
for (int i = n + 1; i <= m; i++)
scanf("%d%d%d", &a[i].z, &a[i].x, &a[i].y), a[i].y++;
for (int i = 1; i <= m; i++) tot = max(tot, a[i].y);
tot++;
memset(c, 0xcf, sizeof(c));
memset(ans, 0x3f, sizeof(ans));
cdqdiv(1, m);
for (int i = 1; i <= m; i++)
if (a[i].z == 2) printf("%d\n", ans[i]);
}
2.基于值域的整体分治
其实,这种方式和前一种是大同小异的。
只不过这种是以值进行分治,上一种是以时间进行分治
bzoj1901
【题意】
公司动态排名开发了一种新型计算机,不再满足于简单地找到给定N个数的第k个最小数。他们开发了一个更强大的系统,对于N个数a[1],a[2],…,a[N],你可以问它:a[i],a[i+1],…,a[j]的第k个最小数是多少?(i<=j,0<k<=j-i+1)。你甚至可以改变一些a[i]的值,并继续查询。
你的任务是为这台计算机编写一个程序
从输入中读取N个数字(1 <= N <= 50000)
处理输入的M指令(1 <= M <= 10000)。这些指令包括查询a[i],a[i+1],…,a[j]的第k个最小数,或将a[i]改为t。
【输入】
输入的第一行是单个数字X(0<X<=4),即输入的测试用例的数量。然后每个X块代表一个测试用例。
每个块的第一行包含两个整数N和M,表示N个数和M个指令。接下来是N行。第i+1行代表数字a[i]。然后是M行,格式如下
Q i j k
或者
C i t
它表示查询a[i],a[i+1],…,a[j]的第k个数,并分别将a[i]变为t。保证在任何操作时间,任何数字a[i]是小于1000000000的非负整数。
两个连续测试用例之间没有空行。
【输出】
对于每个查询操作,输出一个整数来表示结果。(即a[i],a[i+1],…,a[j]的第k个最小数)
两个连续测试用例之间没有空行。
【输入样例】
2
5 3
3 2 1 4 7
Q 1 4 3
C 2 6
Q 2 5 3
5 3
3 2 1 4 7
Q 1 4 3
C 2 6
Q 2 5 3
【输出样例】
3
6
3
6
本来,这道题是要用主席树来做的,但由于没有要求强制在线,所以我们可以用CDQ分治来做。
和前面一样,我们先不看修改操作,就当成不用修改的区间第k小。
引入一个问题,让你在一个区间里面求第k小的数是几。
有这样一种想法,二分每一个数,求小于等于这个数的数有多少个,我们令这个值为t
如果t<=k,就在mid右边,否则就在左边。
加上修改后,同样,我们可以把这个思想放在分治里面
在当前的分治中,我们不断统计值在l-mid中数的个数,如果当前的询问k小于等于这个数,就把这个询问放到l-mid里面分治,否则就令k减去这个数,放到mid+1到r中分治
时间复杂度也是
参考代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 100006, INF = 1e9;
struct rec { int op, x, y, z; } q[3*N], lq[3*N], rq[3*N];
int T, n, m, t, p, a[N], c[N], ans[N];
int cnt, b[3*N];
int ask(int x) {
int y = 0;
for (; x; x -= x & -x) y += c[x];
return y;
}
void change(int x, int y) {
for (; x <= n; x += x & -x) c[x] += y;
}
void solve(int l, int r, int st, int ed) {
if (st > ed) return;
if (l == r) {
for (int i = st; i <= ed; i++)
if (q[i].op > 0) ans[q[i].op] = l;
return;
}
int mid = (l + r)>>1;
int lt = 0, rt = 0;
for (int i = st; i <= ed; i++)
if (q[i].op <= 0) {
if (q[i].y <= mid) change(q[i].x, q[i].z), lq[++lt] = q[i];
else rq[++rt] = q[i];
}
else {
int cnt = ask(q[i].y) - ask(q[i].x - 1);
if (cnt >= q[i].z) lq[++lt] = q[i];
else q[i].z -= cnt, rq[++rt] = q[i];
}
for (int i = ed; i >= st; i--)
if (q[i].op <= 0 && q[i].y <= mid) change(q[i].x, -q[i].z);
for (int i = 1; i <= lt; i++) q[st+i-1] = lq[i];
for (int i = 1; i <= rt; i++) q[st+lt+i-1] = rq[i];
solve(l, mid, st, st + lt - 1);
solve(mid + 1, r, st + lt , ed);
}
int main() {
cin >> T;
while (T--) {
cin >> n >> m;
t = p = cnt = 0;
for (int i = 1; i <= n; i++) {
int val; scanf("%d", &val);
q[++t].op = 0, q[t].x = i, q[t].y = val, q[i].z = 1;
a[i] = val; b[++cnt] = val;
}
for (int i = 1; i <= m; i++) {
char op[2]; scanf("%s", op);
if (op[0] == 'Q') {
int l, r, k; scanf("%d%d%d", &l, &r, &k);
q[++t].op = ++p, q[t].x = l, q[t].y = r, q[t].z = k;
}
else {
int x, y; scanf("%d%d", &x, &y); b[++cnt] = y;
q[++t].op = -1, q[t].x = x, q[t].y = a[x], q[t].z = -1;
q[++t].op = 0, q[t].x = x, q[t].y = y, q[t].z = 1;
a[x] = y;
}
}
sort (b + 1, b + cnt + 1);
int k = unique(b + 1, b + cnt + 1) - b - 1;
for (int i = 1; i <= t; i++)
if (q[i].op <= 0) q[i].y = lower_bound(b + 1, b + k + 1, q[i].y) - b;
solve(0, INF, 1, t);
for (int i = 1; i <= p; i++) printf("%d\n", b[ans[i]]);
}
return 0;
}
来源:CSDN
作者:zsyzClb
链接:https://blog.csdn.net/zsyzClb/article/details/104019470