题目描述
传送门
题目大意:给出一个n个点,m条边的无向图,求每次割掉k(k<=15)条边后图是否联通。
题解
非常不错的一道题,思路非常的巧妙。
首先dfs,找出图中的一棵树,将边分成树边和非树边。
对于每条非树边给他随机一个权值,然后树边的权值是所有覆盖他的非树边的权值异或和。
覆盖的意思是非树边的两个端点分别属于砍断树边后的两个集合。
那么我们可以将非树边的两个端点异或上它的权值,然后再进行一遍dfs,对于一条树边来说覆盖他的权值异或和就是深度较深的端点的子树权值异或和。(画图比较好理解)
如果一个边集存在非空子集的异或和为0, 那么图是不连通的。
然后考虑为什么是对的?
(1)删除了一条树边和所有覆盖他的非树边,那么图一定至少被分成了两部分。因为树边的权值是所有覆盖他的非树边的权值异或和,所以一定存在异或和为0的非空子集。
(2)如果一个点的出边只有树边,且删除了所有与它相连的树边,因为这些树边的异或和一定存在一对相同的或者存在权值本身就是0的边,所以也可以这么做。
代码
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#define N 1000003
using namespace std;
int point[N],v[N],nxt[N],vis[N],tot,val[N],a[N];
int n,m,u[N],q;
int mark[40];
void add(int x,int y)
{
tot++; nxt[tot]=point[x]; point[x]=tot; u[tot]=x; v[tot]=y;
tot++; nxt[tot]=point[y]; point[y]=tot; u[tot]=y; v[tot]=x;
}
void dfs(int x,int fa)
{
vis[x]=1;
for (int i=point[x];i;i=nxt[i]) {
if (v[i]==fa) continue;
if (!vis[v[i]]) dfs(v[i],x);
else val[i>>1]=rand();
}
}
void dfs2(int x,int fa)
{
vis[x]=1;
for (int i=point[x];i;i=nxt[i]) {
if (v[i]==fa) continue;
if (!vis[v[i]]) {
dfs2(v[i],x);
a[x]^=a[v[i]];
val[i>>1]=a[v[i]];
}
}
}
int main()
{
freopen("a.in","r",stdin);
srand(20000219); tot=1;
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++) {
int x,y; scanf("%d%d",&x,&y);
add(x,y);
}
dfs(1,0);
for (int i=1;i<=m;i++) a[u[i<<1]]^=val[i],a[v[i<<1]]^=val[i];
memset(vis,0,sizeof(vis));
dfs2(1,0);
scanf("%d",&q); int cnt=0;
while (q--) {
int k; scanf("%d",&k);
memset(mark,0,sizeof(mark));
bool pd=true;
for (int i=1;i<=k;i++) {
int x; scanf("%d",&x); x^=cnt;
x=val[x];
for (int j=30;j>=0;j--)
if (x&(1<<j)) {
if (!mark[j]) mark[j]=x;
else x^=mark[j];
}
if (!x) pd=false;
}
if (pd) printf("Connected\n"),cnt++;
else printf("Disconnected\n");
}
}