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;
}