bzoj 3569: DZY Loves Chinese II (线性基)

题目描述

传送门

题目大意:给出一个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");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值