一道十分灵活的题(没点脑子真做不出来)。
题目简化:经过某一条路径,使得这条路径能到 u u u 和 v v v 且这条路的 mex \operatorname{mex} mex 值最小。其中 mex \operatorname{mex} mex 是沿途路径权值两两与运算后没出现的最小的自然数。
通过样例可以看出好像答案只有 0 , 1 , 2 0,1,2 0,1,2。
证明: 假设 0 , 1 , 2 0,1,2 0,1,2 都出现过,而有一个大于 2 2 2 的数没出现。那因为 2 2 2 出现过,所以必然有一段数转化为二进制后最后一位与运算后为 0 0 0,因为 0 0 0 和任何数与运算后都是 0 0 0,所以 1 1 1 一定在 2 2 2 的前面,而因为有 1 1 1,所以除了二进制的最后一位,其他的位数都是 0 0 0,但是 2 2 2 在 1 1 1 后面且 2 2 2 的第二位是 1 1 1 不是 0 0 0,所以 1 1 1 根本没出现,正好矛盾。
那什么时候答案是 0 0 0,什么时候答案是 1 1 1,什么时候又是 2 2 2 呢?
答案是 0 0 0:
如果答案是 0 0 0,说明除 0 0 0 之外其他的数都出现过,很容易想到,只要保证这条路径上所有的边权的二进制必然有一位都是 1 1 1 就可以了。
答案是 1 1 1:
这就不太好想了。我们可以这么思考:如果没出现过 1 1 1,说明存在一条分界线,它的前半部分 > 1 \gt1 >1,后半部分则全是 0 0 0。
前半部分 > 1 \gt1 >1 的做法很像上面求答案是 0 0 0 的做法。只需要保证前半部分中有一位全都是 1 1 1 就可以了。但要注意,这一位不能是最后一位,不然前半部分就已经出现 1 1 1 了。
后半部分全是 0 0 0 的情况也很简单。我们只需要先判断后半部分的权值是不是没有任何一位全都是 1 1 1(上面算答案 0 0 0 的时候就判断过,只要答案不是 0 0 0,那就肯定不满足),接着在看看后半部分是不是有一个末位为 0 0 0 的,如果有,那答案就是 1 1 1。
答案是 2 2 2:
只要上面的都不满足,那答案就是 2 2 2 了,具体证明见上。
思路有了,可是上面的做法时间复杂度太高了,怎么优化一下?
这就要请出这道题的精髓了:并查集!
怎么想到并查集的呢?主要还是因为这从始至终其实是一个图,而且又要找 1 1 1 的个数,和并查集中的合并很像,所以就想到并查集了。
首先是合并,怎么合并呢?
我们不难发现,上面的思路中我们都在找一个东西:当前二进制中这一位有 1 1 1 的权值。那很容易想到,我们只需要把这一位有 1 1 1 的权值全连起来,形成一个小的连通块(或者叫树),我在查找从起点到终点之间是否有 1 1 1 时,只需要看看它们两个是否连通了,如果连通了,那就说明必然有一位全都是 1 1 1。
接着是查找。查答案是 0 0 0 的我们说过了,那答案为 1 1 1 的呢?
注意看答案为 1 1 1 的重点:如果最后一位为 0 0 0,那后半部分就必然为 0 0 0。这里主要在说关于最后一位的,所以我们只需要判断每个边权的最后一位是否为 0 0 0,然后再找个超级原点 0 0 0 把所有最后一位是 0 0 0 的起点和终点都合并到这个超级原点上,在判断时只需要看看起点能否到这个超级原点,能到末位就必然为 1 1 1。
证明: 如果我能到超级原点,那在其中就一定有一个点到另一个点之间的边权二进制最后一位为 0 0 0,那么在这个点之后的所有边权与运算后值都为 0 0 0,而在这个点之前的边权与运算后的值都 > 1 \gt1 >1(上面已经否定了前面有 1 1 1 的情况),因此这条路上必然没有 1 1 1。
代码实现:
#include
#define int long long
using namespace std;
int n,m,q,u,v,w,b[100006];
struct mw {
int db[100006];
mw() {
for(int i=1; i<=100006; i++) {
db[i]=i;
}
}
int find(int u) {
return u==db[u]?u:db[u]=find(db[u]);
}
void hb(int u,int v) {
db[find(u)]=find(v);
}
bool pd(int u,int v) {
return find(u)==find(v);
}
} x[36],y[36];
signed main() {
cin>>n>>m;
for(int i=1; i<=m; i++) {
cin>>u>>v>>w;
for(int j=0; j<30; j++) {
if(w&(1<<j)) {
x[j].hb(u,v);
}
}
if(!(w&1)) {
b[u]=b[v]=1;
}
}
for(int i=1; i<30; i++) {
y[i]=x[i];
for(int j=1; j<=n; j++) {
if(b[j]) {
y[i].hb(j,0);
}
}
}
cin>>q;
while(q--) {
cin>>u>>v;
bool flag=false;
for(int i=0; i<30; i++) {
if(x[i].pd(u,v)) {
cout<<"0"<<endl;
flag=true;
break;
}
}
if(flag) {
continue;
}
for(int i=1; i<30; i++) {
if(y[i].pd(u,0)) {
cout<<"1"<<endl;
flag=true;
break;
}
}
if(flag) {
continue;
}
cout<<"2"<<endl;
}
return 0;
}