【做题笔记】CF1659E AND-MEX Walk
Problem
题目大意:
给你一张 \(n\) 个点 \(m\) 条边的无向联通图,边有边权。
定义一条依次经过 \(e_1,e_2,\cdots,e_k\) 这些边的路径的权值为 \(mex\{w_1,w_2\&w_3,\cdots,w_1\&w_2\&\cdots\&w_k\}\)。其中 \(w_i\) 为边 \(e_i\) 的边权。(tips:路径可以重复经过一些边和一些点。)
现在给你 \(q\) 个询问,问从 \(u\) 到 \(v\) 的路径的权值的最小值。
Solution
首先有一个结论,\(w_1,w_2\&w_3,\cdots,w_1\&w_2\&\cdots\&w_k\) 是一个递减的序列,且若第 \(i\) 个数二进制下某一位上为 \(0\),则第 \(i \le j \le k\) 个数这一位也必然为 \(0\)。原因显然。
然后又有一个结论,任意路径的权值只能是 \(0,1,2\) 中的某一个。
证明:
若 \(w_1\&w_2\&\cdots\&w_k \not= 0\),则这条路径的权值为 \(0\)。
在除掉上一种情况后,若存在某个 \(i\) 使得 \(w_i\) 为偶数,且序列中 \(i\) 之前不存在 \(1\) 则整个序列必然不可能出现 \(1\),故权值为 \(1\)。
假设存在一条路径,序列中某一位是 \(2\),某一位是 \(1\)。由第一个结论得 \(2\) 必然在 \(1\) 前面出现。但 \(2\) 在二进制下第 \(0\) 位为 \(0\),\(1\) 在二进制下第 \(0\) 位为 \(1\),与第一条结论冲突。故序列中不可能同时存在 \(2\) 和 \(1\),故除了上面两种情况,其他情况的权值均为 \(2\)。
接下来考虑一个一个判断。
要判断是否存在权值为 \(0\) 的路径,实际上就是要判断是否存在一条路径使得路径上所有边权按位与后不为 \(0\),也就是找一条路径使得路径上的边权在二进制下某一位均为 \(1\)。
这个问题很好解决,只需要按位建立并查集,第 \(i\) 个并查集只使用边权二进制下第 \(i\) 位为 \(1\) 的边,最后判联通即可。
若一条路径边权为 \(1\),则其前半段路径上边权按位与之后不为 \(1\),后半段第一条边边权为偶数。
对于第一个条件,可以用类似于判 \(0\) 的方法,只查询除了第 \(0\) 位以外的并查集即可。
对于第二个条件,由于可以重复经过一些边,所以我们可以让路径一直连到某一条边权为偶数的边,走一下再退回来。所以我们可以标记有边权为偶数的边直接连接的点,只要于这些点中的某一个联通即可。由于整张图是联通的,而走完这条边之后可以随便走,所以之后必然存在答案。但是如果每个集合都枚举这些点判是否联通,复杂度是 \(O(nq)\) 的,所以我们可以建立一个虚点 \(0\),把所有于偶数边权边直接相连的点与 \(0\) 合并,这样只需查询是否与 \(0\) 联通。
若以上两个条件都不满足,则最小权值只能是 \(2\)。
Code
//Think twice,code once.
#include<cstdio>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int n,m,f[100005];
struct bin
{
int fa[100005];
bin(){for(int i=0;i<=100000;i++) fa[i]=i;}
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
void merge(int x,int y){fa[find(x)]=find(y);return ;}
int query(int x,int y){return find(x)==find(y);}
}x[35],y[35];
int main()
{
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 i=0;i<30;i++)
if((w>>i)&1) x[i].merge(u,v);
if(!(w&1)) f[u]=f[v]=1;
}
for(int i=1;i<30;i++)
{
y[i]=x[i];
for(int j=1;j<=n;j++)
if(f[j]) y[i].merge(j,0);
}
int q;
scanf("%d",&q);
while(q--)
{
int u,v;
scanf("%d%d",&u,&v);
for(int i=0;i<30;i++)
if(x[i].query(u,v)){puts("0");goto NXT;}
for(int i=1;i<30;i++)
if(y[i].query(u,0)){puts("1");goto NXT;}
puts("2");
NXT:;
}
return 0;
}