AND-MEX Walk
算法
性质
首先容易观察到
中 集合
显然是单调不增的
显然答案取决于集合的后几位
考虑拆位
拆位
- 答案为 \(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\)
过了一段时间考虑复习一下, 对这题也想有一个更深的了解
证明 \(ans \leq 2\)
大眼瞪大样例得出
使用反证法, 假设同时出现了 \(2, 1, 0\) 三个值
你发现因为集合 \(\mathbb{V}\) 单调不增, \(2, 1, 0\) 出现的顺序是固定的, 也就是说, 集合中需要出现
显然 \(x\) 不存在, 所以得证
求出答案
答案为 \(0\)
这种情况下, 必须满足在所有二进制位中, 有至少一位不出现 \(0\) , 考虑把每一位的 \(1\) 连边, 最终如果 \(u, v\) 联通, 即可证明答案为 \(0\)
答案为 \(1\)
考虑这种情况下, 需要满足什么样的条件才能构造出不出现 \(1\) 的情况
先考虑什么时候会出现 \(1\) :
对于 \(1 \sim len\) 位上, 已经被与成 \(0\) , 但是第 \(0\) 位上还是 \(1\)
也就是说, 只要出现一种情况, 使得 \(1 \sim len\) 位中有一个比第 \(0\) 位后与成 \(0\) 即可
怎么实现, 你发现第 \(0\) 位与成 \(0\) , 仅当出现一个 \(1 \sim len\) 的位, 可以连通到一个第 \(0\) 位为 \(0\) 的边上即可
答案为 \(2\)
上面的情况都不满足, 答案即为 \(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;
}
总结
注意并查集的初始化和查询
答案可能性很少时, 考虑分类讨论
需要注意的是,「不满足」一个条件, 可以转化为不满足「满足」的条件
并查集常用来处理联通问题

浙公网安备 33010602011771号