Parallel Binary Search 并行二分
引入
有一些问题要求我们回答若干询问。这些询问通常都是互不相干的。有时候二分能够以 \(O(nlog(n))\) 的复杂度单独解决一个询问,但是对于所有的询问都采用二分的话就是 \(O(qnlog(n))\) 的复杂度会超时(可以看 ABC394 G 体会一下)。于是就有了并行二分这一解决方法。
并行二分
我们观察到在对所有的询问都进行二分时,重复 check 了非常多次。假设每个询问所设定的二分区间是 \([L, R]\),那么每个询问我们都对 \(L + R \over 2\) 这个点 check 了。实际上我们只需要 check 这个中点一次。并行二分所做的就是简化掉这些重复的 check。具体的做法就是对所有的询问都初始化二分区间为 \([L, R]\),然后在接下来的二分中先根据每个询问的 \(mid\) 值将询问归类, \(mid\) 值相同的询问在一起。然后对于所有可能的 \(mid\) 值依据题意从小到大或者从大到小 check,看是否满足一开始归为这类的询问。
比如 ABC394 G 并形二分的过程就是:
// code ...
std::vector A(q, 0), B = A, Y = A, C = A, D = A, Z = A, L(q, 1), R(q, M);
std::vector mid(M + 1, std::vector<int>{});
while (true) {
// 将上次归的类清空
// M 是二分区间的右区间
for (int i = 1; i <= M; i++) {
mid[i].clear();
}
bool ok = true; // 是否所有询问都找到了答案
for (int i = 0; i < q; i++) {
if (L[i] <= R[i]) { // 这里的条件就是二分结束的条件,个人喜欢带等号
ok = false;
// 归类
mid[L[i] + R[i] >> 1].push_back(i);
}
}
if (ok) { // 都找到答案了就 break 掉
break;
}
for (int i = 0; i < h * w; i++) {
fa[i] = i;
}
// 从大到小遍历
for (int i = M, j = 0; i >= 1; i--) {
if (mid[i].empty()) {
continue;
}
// 加入符合条件的边
while (j < edge.size() && edge[j][2] >= i) {
merge(edge[j][0], edge[j][1]);
j++;
}
for (auto &id : mid[i]) {
// 如果在一个连通块里
if (get(A[id] * w + B[id]) == get(C[id] * w + D[id])) {
L[id] = i + 1;
}
// 否则
else {
R[id] = i - 1;
}
}
}
}
// code ...
觉得没看懂也可以看看这篇:Parallel Binary Search [tutorial]
浙公网安备 33010602011771号