Not Another Path Query Problem

Not Another Path Query Problem

图层拆分(路径长度,与运算)

题意简述

给定一张 \(n\) 个点、\(m\) 条无向边的图,边权为 \(w_i\)(\(0 \le w_i < 2^{60}\))。
定义一条路径的价值为路径上所有边权的按位与(bitwise AND)。
给定阈值 \(V\),每次询问两点 \(u, v\) 之间是否存在一条价值至少为 \(V\) 的路径。

核心观察

路径的价值 \(X\) 是路径上所有边权的按位与结果。
我们需要判断是否存在路径使得 \(X \ge V\)。

按位比较两个数 \(X\) 和 \(V\) 时,从最高位开始,找到第一个不同的位:

  • 如果该位上 \(X=1\) 而 \(V=0\),则 \(X > V\);
  • 如果该位上 \(X=0\) 而 \(V=1\),则 \(X < V\);
  • 如果所有位都相同,则 \(X = V\)。

因此,\(X \ge V\) 可以拆解为以下两类情况:

情况 1:\(X\) 包含 \(V\) 的所有 1 位

即对于所有满足 \(V\) 的第 \(i\) 位为 1 的位,\(X\) 的第 \(i\) 位也必须为 1。
此时 \(X\) 至少等于 \(V\)(可能在某些 \(V=0\) 的位上 \(X=1\),那就更大)。
这等价于要求路径上所有边权都包含 \(V\) 的所有 1 位,即:

\[(w \& V) == V \]

我们记这个掩码为 \(mask = V\)。

情况 2:存在某个 \(V\) 为 0 的位 \(i\),使得 \(X\) 在该位为 1,且所有高于 \(i\) 的、\(V\) 为 1 的位,\(X\) 也为 1

这样从高位比较时,第一个不同的位就是第 \(i\) 位(\(V=0, X=1\)),所以 \(X > V\)。
低于 \(i\) 的位任意。

对于每个 \(V\) 中为 0 的位 \(i\),我们构造一个掩码:

\[mask = (\text{所有高于 } i \text{ 且 } V \text{ 为 1 的位}) \mid (1 \ll i) \]

然后只保留满足 \((w \& mask) == mask\) 的边,判断询问点对是否连通。

注意:不需要枚举低于最低位 1 的那些 0 位。因为如果 \(X\) 在这些位上为 1,并且高于该位的 \(V\) 的 1 位也都为 1,那么实际上 \(X\) 已经包含了 \(V\) 的所有 1 位,属于情况 1,会被 \(mask = V\) 检查到。

算法步骤

  1. 读入所有边和询问。
  2. 构造所有需要的掩码 mask:
    • 首先加入 mask = V。
    • 找到 \(V\) 的最低为 1 的位 lowbit。
    • 枚举 \(i\) 从 61 到 lowbit + 1:
      • 如果 \(V\) 的第 \(i\) 位为 1,跳过;
      • 否则,构造 mask:

        \[mask = ((1 \ll 62) - 1) - ((1 \ll i) - 1) \]

        然后 mask = mask & V,再 mask |= (1 \ll i)。
      • 加入 mask 列表。
    • 去重(可选)。
  3. 对于每个 mask:
    • 初始化并查集。
    • 遍历所有边,如果 (w & mask) == mask,则合并边的两个端点。
    • 遍历所有尚未确定答案的询问,如果两点在同一个连通块中,则该询问答案为 Yes,并标记为已确定。
  4. 输出所有询问的答案。

正确性证明

  • 如果存在一条路径,其按位与结果 \(X \ge V\),那么根据按位比较规则,要么 \(X\) 包含 \(V\) 的所有 1 位(情况 1),要么存在某个 \(V=0\) 的位 \(i\) 使得 \(X\) 在第 \(i\) 位为 1 且高于 \(i\) 的 \(V\) 的 1 位都为 1(情况 2)。这两种情况分别对应我们枚举的 mask = V 和某个构造的 mask。在这两种情况下,路径上的每条边都满足 (w & mask) == mask,因此在该 mask 下,询问点对一定连通。
  • 反之,如果在某个 mask 下询问点对连通,那么存在一条路径,其所有边都满足 (w & mask) == mask,因此路径的按位与结果 \(X\) 也包含 mask 的所有 1 位。对于 mask = V,\(X \supseteq V\),所以 \(X \ge V\);对于构造的 mask,\(X\) 在高于 \(i\) 的 \(V\) 的 1 位上为 1,在第 \(i\) 位为 1 而 \(V\) 为 0,因此 \(X > V\)。两种情况都满足 \(X \ge V\)。

复杂度分析

  • 掩码数量最多为 \(62\) 个(\(V\) 的位数)。
  • 每个掩码需要遍历 \(m\) 条边和 \(q\) 个询问。
  • 并查集操作近似 \(O(\alpha(n))\)。
  • 总时间复杂度:\(O(62 \cdot (m + q) \cdot \alpha(n))\)。
  • 空间复杂度:\(O(n + m + q)\)。

对于 \(n \le 10^5, m, q \le 5 \times 10^5\),该复杂度完全可以在 4 秒内通过。

实现细节与注意事项

  1. \(V = 0\) 的情况
    此时 lowbit 循环需要防止死循环。可以特判:如果 \(V=0\),直接加入 mask = 0,它保留所有边,只需判断原图连通性。

  2. 并查集初始化
    init() 中需要将 pa 重置为自身,sz 全部填充为 1。不要使用 sz.clear(),否则会导致越界。

  3. vis 数组大小
    用于标记询问是否已确定答案,大小应为 \(q + 1\),而不是 \(m + 1\)。

  4. 枚举范围
    边权 \(< 2^{60}\),所以最高位到 60 即可。使用 1LL << 62 可以安全地构造高位掩码。

  5. 询问存储
    使用 vector<pair<int,int>> 或 vector<tuple<int,int,int>> 存储询问,配合 vis 数组跳过已解决的询问。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MOD = 1e9 + 7;

struct dsu {
    int n;
    vector<int> pa, sz;
    dsu(int _n) : n(_n), pa(_n + 1), sz(_n + 1, 0) {init();}
    void init() {
        iota(pa.begin(), pa.end(), 0);
        fill(sz.begin(), sz.end(), 1);
    }

    int findu(int x) {
        if(pa[x] == x) return x;
        return pa[x] = findu(pa[x]);
    }

    void unite(int x, int y) {
        int rx = findu(x), ry = findu(y);
        if(rx != ry) {
            if(sz[rx] <= sz[ry]) pa[rx] = ry;
            else pa[ry] = rx;
        }
    }
};

void solve() {
    int n, m, q; ll v; cin >> n >> m >> q >> v;
    vector<tuple<int, int, ll>> edge;
    for(int i = 1; i <= m; i ++) {
        int a, b; ll w; cin >> a >> b >> w;
        edge.emplace_back(a, b, w);
    }
    vector<pair<int, int>> query(q + 1);
    vector<int> vis(q + 1, 0);
    for(int i = 1; i <= q; i ++) {
        int u, v; cin >> u >> v;
        query[i] = {u, v};
    }
    
    vector<ll> bitmask;
    int lowbit = 0;
    while(((v >> lowbit) & 1) == 0 && v != 0) lowbit ++;
    for(int i = 61; i > lowbit; i --) {
        if(((v >> i) & 1) == 1) continue;
        ll mask = ((1LL << 62) - 1) - ((1LL << i) - 1);
        mask = mask & v;
        mask |= (1LL << i);
        bitmask.push_back(mask);
    }
    bitmask.push_back(v);

    int len = bitmask.size();
    vector<int> ans(q + 1, 0);
    dsu dsf(n);
    for(int i = 0; i < len; i ++) {
        dsf.init();
        ll mask = bitmask[i];
        for(int i = 0; i < m; i ++) {
            auto [u, v, w] = edge[i];
            if((w & mask) == mask) dsf.unite(u, v);
        }

        for(int i = 1; i <= q; i ++) {
            if(vis[i]) continue;
            auto [x, y] = query[i];
            int rx = dsf.findu(x), ry = dsf.findu(y);
            if(rx == ry) ans[i] = 1, vis[i] = 1;
        }
    }
    for(int i = 1; i <= q; i ++) cout << (ans[i] ? "Yes\n" : "No\n");
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int _ = 1; //cin >> _;
    while(_--) solve();
    return 0;
}
posted @ 2026-10-11 21:10  thenrify  阅读(5)  评论(0)    收藏  举报