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]

posted @ 2025-02-28 12:50  Young_Cloud  阅读(45)  评论(0)    收藏  举报