【题解】 [AHOI2013]连通图 hash+差分/线段树分治+并查集 Luogu5227

Legend

给定一个无向连通图和若干个小集合,每个小集合包含一些边,对于每个集合,你需要确定将集合中的边删掉后改图是否保持联通。集合间的询问相互独立

定义一个图为联通的当且仅当对于任意的两个顶点,都存在一条路径连接它们。

点数 (1 le n le 10^5),集合数量 (1 le k le 10^5),边数量 (1 le m le 2 imes 10^5),集合大小 (1 le c le 4)

Editorial

一眼线段树分治对吧,但是这么做是非常没有意思的。

考虑直接 ( m{dfs}) 建立一棵生成树,剩下的都是返祖边,不妨看成一次覆盖操作。

这棵树有以下性质:

  • 仅割掉返祖边图一定联通。
  • 一条树边割掉之后,必须要把所有覆盖了它的返祖边割掉才能使得图不联通。

所以我们把每一条返祖边设定一个随机 hash 值,把每一条树边 hash 值设置成所有覆盖了它的返祖边的 hash 异或和。

这样每次查询只需要看是否存在一个包含返祖边的子集 hash 值异或和为 (0)

每次查询的错误概率为:( extrm{unsigned int}) 范围选出 (c) 个整数,存在一个子集异或为 (0) 的概率。这个东西我显然是不会算的。

复杂度 (O(malpha(n)+2^ck))

Code

代码实现的时候我很懒,没有检查集合是否有返祖先边。

#include <bits/stdc++.h>

using namespace std;

int read(){
	char k = getchar(); int x = 0;
	while(k < '0' || k > '9') k = getchar();
	while(k >= '0' && k <= '9')
		x = x * 10 + k - '0' ,k = getchar();
	return x;
}

const int MX = 2e5 + 233;

int head[MX] ,tot = 1;
struct edge{
	int node ,next ,w;
}h[MX << 1];
void addedge(int u ,int v ,int w ,int flg = 1){
	h[++tot] = (edge){v ,head[u] ,w} ,head[u] = tot;
	if(flg) addedge(v ,u ,w ,false);
}

struct E{
	int u ,v ,w;
}e[MX];

int vis[MX] ,cnt ,dis[MX];
void dfs(int x ,int f = 0){
	vis[x] = ++cnt;
	for(int i = head[x] ,d ; i ; i = h[i].next){
		d = h[i].node;
		if(d == f) continue;
		if(vis[d]){
			if(vis[d] < vis[x]){
				dis[d] ^= h[i].w;
				dis[x] ^= h[i].w;
			}
		}else{
			dfs(d ,x);
			dis[x] ^= dis[d];
			e[i / 2].w = dis[d];
		}
	}
}

int main(){
	int n = read() ,m = read();
	for(int i = 1 ; i <= m ; ++i){
		e[i].u = read() ,e[i].v = read();
		e[i].w = 1u * i * i + 1u * rand();
		addedge(e[i].u ,e[i].v ,e[i].w);
	}
	dfs(1);
	int k = read();
	while(k--){
		int s = read();
		int a[4];
		for(int i = 0 ; i < s ; ++i) a[i] = read();
		int ok = 0;
		for(int i = 1 ; i < 1 << s && !ok ; ++i){
			int v = 0;
			for(int j = 0 ; j < s ; ++j){
				if((i >> j) & 1) v ^= e[a[j]].w;
			}
			if(v == 0) ok = true;
		}
		printf("%sonnected
" ,ok ? "Disc" : "C");
	}
	return 0;
}
原文地址:https://www.cnblogs.com/imakf/p/13688027.html