原题链接: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,...,wk−1} ,那么 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&...&wk−1} 就是我们路径的价值。( 其中 & \& & 为二进制的与的操作, 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 u→v 联通即可,比较简单的一个想法就是,对每个二进制位开一个并查集,然后直接判连通性即可。
再考虑 m e x { S } = 1 mex\{S\}=1 mex{S}=1 的情况,假设我们 u → v u \rightarrow v u→v 的 与操作 不能使得某个二进制位上一直包含有 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) (u→v) 的路径使得 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;
}