AND-MEX Walk

算法

性质

首先容易观察到

\[\text{mex} (w_1, w_1 \And w_2, w_1 \And w_2 \And w_3,\cdots , w_1 \And w_2 \And w_3 \And \cdots w_k) \]

中 集合

\[{w_1, w_1 \And w_2, w_1 \And w_2 \And w_3,\cdots , w_1 \And w_2 \And w_3 \And \cdots w_k} \]

显然是单调不增的

显然答案取决于集合的后几位
考虑拆位

拆位

  • 答案为 \(0\)

当集合中不存在 \(0\) 时, 答案显然为 \(0\)
即存在一条路使得路径上的边权集合, 有一位都为 \(1\)
考虑用并查集实现

对每一位, 如果当位为 \(1\) , 那么加入并查集
判断答案是否为 \(0\) , 即为判断两点是否连通, 若连通则必有一方法使得 \(0\) 不在集合中

  • 答案为 \(1\)

由于现在已经证明了所有路径 \(u \rightarrow v\) 都不可能出现 \(0\)
只需要证明不能出现 \(1\)

若一条路径边权为 \(1\),则其前一段路径上边权按位与之后不为 \(1\),后一段第一条边边权为偶数。

分以下两种情况讨论

  • 前一段路径上不为 \(1\), 同答案为 \(0\) 时的打法, 注意不统计第 \(0\) 位即可
  • 只要存在第 \(0\) 位上不为 \(1\) 的数, 就可以把这一位上的 \(1\) 消掉, 又因为

已经证明了所有路径 \(u \rightarrow v\) 都不可能出现 \(0\)

所以只要出发点能与某一个边权为偶数(即第 \(0\) 位上不为 \(1\) 的数)相连, 就有正确答案

实现2.
所以我们可以建立一个虚点 \(0\),把所有于偶数边权边直接相连的点与 \(0\) 合并,这样只需查询是否与 \(0\) 联通

  • 答案为 \(\gt 2\)
    观察到如果有 \(2\) , \(1\) , \(0\) 存在
    那么必须存在 \(2 \And a = 1\), 而 \(a\) 不存在
    所以答案只能为 \(0, 1, 2\)

代码

#include <bits/stdc++.h>
const int MAXN = 1e5 + 20;

int n, m;

/*并查集*/
class Union_Set
{
    private:

    public:
        int fa[MAXN];

        void init() //
        {
            for (int i = 1; i <= 1e5; i++)
            {
                fa[i] = i;
            }
        }

        int find(int x)
        {
            return fa[x] = (x == fa[x]) ? x : find(fa[x]);
        }

        void merge(int u, int v)
        {
            int fa_u = find(u);
            int fa_v = find(v);

            fa[fa_u] = fa_v;
        }

        bool query(int u, int v)
        {
            return find(u) == find(v); //
        }
} US_all[32], US_even[32];

/*并查集判断每一位的情况*/

int main()
{

    for (int i = 0; i <= 30; i++)
    {
        US_all[i].init();
        US_even[i].init();
    }

    scanf("%d %d", &n, &m);

    for (int i = 1; i <= m; i++)
    {
        int u, v, w;
        scanf("%d %d %d", &u, &v, &w);
        for (int j = 0; j <= 30; j++)
        {
            if ((w >> j) & 1)
            {
                US_all[j].merge(u, v);
                if(j != 0)
                    US_even[j].merge(u, v);
            }
                
        }

        if (!(w & 1))
        {
            for(int j = 0; j <= 30; j++)
            {
                US_even[j].merge(u, 0);
                US_even[j].merge(v, 0);
            }
        }
    }

    int q;
    
    scanf("%d", &q);
    while(q--)
    {
        int s, t;
        scanf("%d %d", &s, &t);

        /*判断答案是否为 0*/
        bool Answer_is_0 = false;
        for (int i = 0; i <= 30; i++)
        {
            if(US_all[i].query(s, t))
            {
                Answer_is_0 = true;
                break;
            }
        }

        if(Answer_is_0)
        {
            printf("0\n");
            continue;
        }

        /*判断答案是否为 1*/
        bool Answer_is_1 = false;
        for(int i = 1; i <= 30; i++) //这里不取到 1 的原因是你至少不能有 1 吧
        {
            if(US_even[i].query(s, 0)) //只需要判断出发点能否使集合中存在 > 1, 之前已经判断了集合中存在 0, 余下不管怎么走都能到达目的地
            {
                Answer_is_1 = true;
                break;
            }
        }

        if (Answer_is_1)
        {
            printf("1\n");
            continue;
        }

        printf("2\n");
    }

    return 0;
}

总结

注意并查集的初始化和查询

posted @ 2024-10-06 17:00  Yorg  阅读(3)  评论(0编辑  收藏  举报