题解:CF1659E AND-MEX Walk

一道十分灵活的题(没点脑子真做不出来)。

题目简化:经过某一条路径,使得这条路径能到 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;
}

你可能感兴趣的:(题解,算法)