Codeforces Round 782 (Div. 2) E. AND-MEX Walk(思维+并查集)

原题链接:E. AND-MEX Walk


题目大意:


给出一张 n n n 个点 m m m 条边的无向图,边带有边权。

我们定义一条路径的价值为:

假设我们经过了 k k k 个点(点和边都可重复经过),且按顺序经过的边的权值分别为 { w 1 , w 2 , w 3 , . . . , w k − 1 } \{w_1,w_2,w_3,...,w_{k-1}\} {w1,w2,w3,...,wk1} ,那么 m e x { w 1 , w 1 & w 2 , . . . , w 1 & w 2 & . . . & w k − 1 } mex\{w_{1},w_{1}\&w_{2},...,w_{1}\&w_{2}\&...\&w_{k-1}\} mex{w1,w1&w2,...,w1&w2&...&wk1} 就是我们路径的价值。( 其中 & \& & 为二进制的与的操作, m e x { S } mex\{S\} mex{S} 为集合 S S S 中没有出现过的最小的自然数)。

现在给出 q q q 次询问,每次给出两个不同的点 u , v u,v u,v ,问你从 u u u 出发经历一些点到达点 v v v 所能获得的路径的价值的最小值为多少。

解题思路:


答案只会出现 0 , 1 , 2 0,1,2 0,1,2 三种情况,感性证明一下:

假设要使得我们的 m e x { S } ≥ 3 mex\{S\}\geq3 mex{S}3 ,那么就要让我们的 S = { 0 , 1 , 2 } S=\{0,1,2\} S={0,1,2} 在我们的序列中全都出现过。

假设我们经历了一系列前缀与的情况,最后剩下一个 2 2 2 ,现在我们还要走过一系列的点,然后使得 1 1 1 出现在我们的序列中(或者剩下一个 1 1 1,再使得 2 2 2 出现)。

但注意到 2 = ( 10 ) 2 , 1 = ( 01 ) 2 2=(10)_{2},1=(01)_{2} 2=(10)2,1=(01)2,它们相互与起来为 0 = ( 00 ) 2 0=(00)_{2} 0=(00)2 ,那么想要使得 1 , 2 1,2 1,2 都同时出现在 m e x { S } mex\{S\} mex{S} 序列中同时出现,由于有 与运算 这个操作在,两者必然不可能同时出现。

对于上面所讨论的情况而言,我们最后一定只会只有 { 1 , 0 } , { 2 , 0 } , \{1,0\},\{2,0\}, {1,0},{2,0}, ,即答案一定会被限制在 { 0 , 1 , 2 } \{0,1,2\} {0,1,2} 中。

想想怎么做:

先考虑 m e x { S } = 0 mex\{S\}=0 mex{S}=0 的情况,对于这种情况来说,我们的 与操作 只要保证选出一些边,且某个二进制位上一直包含有 1 1 1 ,且这些边使得我们 u → v u \rightarrow v uv 联通即可,比较简单的一个想法就是,对每个二进制位开一个并查集,然后直接判连通性即可。

再考虑 m e x { S } = 1 mex\{S\}=1 mex{S}=1 的情况,假设我们 u → v u \rightarrow v uv与操作 不能使得某个二进制位上一直包含有 1 1 1 ,即我们 u , v u,v u,v 不在一个第 i i i 个二进制位的并查集之内( 没有任何一个 2 i 2^{i} 2i 位在路径上是一直出现的 ),那么这一定会使得我们 0 0 0 出现在 S S S 当中,因为此时我们必定要选出一条不包含 2 i 2^{i} 2i 的边( 否则 u u u 无法走到 v v v ),然后走过这条边。

那么什么时候会出现 1 1 1 呢?

给出一个结论:按上面的来说,我们与序列一定是 S = { a , b , c , d , . . . , 0 , 0 , 0 , 0 , . . . } S=\{a,b,c,d,...,0,0,0,0,...\} S={a,b,c,d,...,0,0,0,0,...}只要我们能使得那些二进制第 2 0 2^{0} 20 位不 单独出现 a , b , c , d , . . . , 0 a,b,c,d,...,0 a,b,c,d,...,0 这一段之间即可,怎么保证?我们只需要有至少存在某个二进制位 2 i ( i ≠ 0 ) 2^{i}(i\neq0) 2i(i=0) 和它一起同时出现,或者 2 0 2^{0} 20 不出现即可。那么我们最开始先保留二进制 2 i 2^{i} 2i 位,在此基础上我们任选一条边与起来,使得 2 0 2^{0} 20 这一位变成 0 0 0 ,最后再走任意的 ( u → v ) (u \rightarrow v) (uv) 的路径使得 m e x { S } = 0 mex\{S\}=0 mex{S}=0 即可。

可知这样做,我们最开始由于存在 2 i 2^{i} 2i 不会出现单独的 2 0 2^{0} 20,我们最后将 2 i 2^{i} 2i 消掉时,我们也能保证 2 0 2^{0} 20 在最开始时被消掉了,故 2 0 2^{0} 20 不可能单独出现。

要这样做,我们要找出所有能让 2 0 2^{0} 20 位为 0 0 0 的边,再找一个含有 2 i ( i ≠ 0 ) 2^{i}(i\neq0) 2i(i=0) 的并查集,和这些边合并起来,再判断 u , v u,v u,v 是否在这些新的并查集中即可。

再考虑 m e x { S } = 2 mex\{S\}=2 mex{S}=2 的情况,显然答案只会有 0 , 1 , 2 0,1,2 0,1,2 ,只要上面的情况不满足,那么我们的答案就一定会是 2 2 2 了。

时间复杂度: O ( n log ⁡ w + ( m + q ) α ( n ) log ⁡ w ) O(n\log w+(m+q)\alpha(n)\log w) O(nlogw+(m+q)α(n)logw)

AC代码:

#include <bits/stdc++.h>
using namespace std;

using PII = pair<int, int>;
using i64 = long long;

struct DSU {
    vector<int> fa, siz;
    DSU(int n) : fa(n + 1), siz(n + 1, 1) { iota(fa.begin(), fa.end(), 0); };
    int find(int x) {
        while (x != fa[x]) x = fa[x] = fa[fa[x]];
        return x;
    }
    int size(int x) { return siz[find(x)]; }
    bool same(int x, int y) { return find(x) == find(y); }
    bool merge(int x, int y) {
        x = find(x), y = find(y);
        if (x == y) return false;
        siz[y] += siz[x];
        fa[x] = y;
        return true;
    }
};

void solve() {
    int n, m;
    cin >> n >> m;

    vector dsu1(31, DSU(n));

    vector<bool> mark(n + 1);
    for (int i = 1; i <= m; ++i) {
        int u, v, w;
        cin >> u >> v >> w;
        for (int j = 0; j <= 30; ++j) {
        	//至少包含 2^i 的并查集
            if (w >> j & 1) {
                dsu1[j].merge(u, v);
            }
        }
        if (~w & 1) {
            mark[u] = mark[v] = true;
        }
    }

    auto dsu2 = dsu1;//判1的并查集
    for (int j = 0; j <= 30; ++j) {
        for (int i = 1; i <= n; ++i) {
        	//将至少包含 2^i 的并查集和 2^0 = 0 的边并起来 保证最后 2^0 恒为0
            if (mark[i]) {
                dsu2[j].merge(i, 0);
            }
        }
    }

    int q;
    cin >> q;

    for (int i = 1; i <= q; ++i) {
        int u, v;
        cin >> u >> v;

        int ans = 2;//先设为2
        for (int j = 0; j <= 30; ++j) {
	        //是否可以为0
            if (dsu1[j].same(u, v)) {
                ans = min(ans, 0);
            }
        }

        for (int j = 1; j <= 30; ++j) {
        	//是否可以为1
            if (dsu2[j].same(u, 0)) {
                ans = min(ans, 1);
            }
        }
		//否则为2
        cout << ans << '\n';
    }
}

signed main() {

    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    int t = 1; //cin >> t;
    while (t--) solve();

    return 0;
}
  • 23
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qq_17175035

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值