JZOJ 1321. 灯

Description

  贝希和她的闺密们在她们的牛棚中玩游戏。但是天不从人愿,突然,牛棚的电源跳闸了,所有的灯都被关闭了。贝希是一个很胆小的女生,在伸手不见拇指的无尽的黑暗中,她感到惊恐,痛苦与绝望。她希望您能够帮帮她,把所有的灯都给重新开起来!她才能继续快乐地跟她的闺密们继续玩游戏!
  牛棚中一共有N(1 <= N <= 35)盏灯,编号为1到N。这些灯被置于一个非常复杂的网络之中。有M(1 <= M <= 595)条很神奇的无向边,每条边连接两盏灯。
  每盏灯上面都带有一个开关。当按下某一盏灯的开关的时候,这盏灯本身,还有所有有边连向这盏灯的灯的状态都会被改变。状态改变指的是:当一盏灯是开着的时候,这盏灯被关掉;当一盏灯是关着的时候,这盏灯被打开。
  问最少要按下多少个开关,才能把所有的灯都给重新打开。
  数据保证至少有一种按开关的方案,使得所有的灯都被重新打开。

Input

  第一行:两个空格隔开的整数:N和M。

  第二到第M+1行:每一行有两个由空格隔开的整数,表示两盏灯被一条无向边连接在一起。没有一条边会出现两次。

Output

  第一行:一个单独的整数,表示要把所有的灯都打开时,最少需要按下的开关的数目。

Sample Input

5 6
1 2
1 3
4 2
3 4
2 5
5 3

Sample Output

3

Data Constraint

Hint

【样例说明】

  一共有五盏灯。灯1、灯4和灯5都连接着灯2和灯3。按下在灯1、灯4和灯5上面的开关。

Solution

  • 这道题看上去无从下手, N35 的范围令人有些不知所措。

  • 事实上这道题的解法十分巧妙,用到了 折半搜索 的巧法。

  • 分别搜索 217 218 两部分,记录其中一部分的答案,用哈希表储存。

  • 处理另一部分时从哈希表中查找,异或出答案,相加即可。

  • 这样的时间复杂度折半为 2N2 ,可以通过本题。

  • 详细代码见下面:(C++选手可以使用 STL 的 Map 库代替哈希,代码为注释部分,简单却较慢)

  • 据说 这题可以用高斯消元来做,一盏灯与周围灯的异或值为 1 表示亮,列出 N 个方程解之。

Code

#include<cstdio>
//#include<map>
using namespace std;
const int N=36,mo=1234321;
//map<long long,int>mp;
int n,m,m1,m2,ans=N;
int a[N][N];
long long p[N],f[N],g[mo],h[mo];
bool bz[N];
inline int read()
{
    int X=0,w=1; char ch=0;
    while(ch<'0' || ch>'9') {if(ch=='-') w=-1;ch=getchar();}
    while(ch>='0' && ch<='9') X=(X<<3)+(X<<1)+ch-'0',ch=getchar();
    return X*w;
}
inline int min(int a,int b)
{
    return a<b?a:b;
}
inline int hash(long long x)
{
    int y=x%mo;
    while(h[y] && h[y]!=x) y=(y+1)%mo;
    return y;
}
inline void dfs1(int x)
{
    if(x>m1)
    {
        long long num=0;
        int s=1;
        for(int i=1;i<=m1;i++)
            if(bz[i]) num^=f[i],s++;
        int k=hash(num);
        if(!h[k]) h[k]=num,g[k]=s; else
            g[k]=min(g[k],s);
        /*if(!mp[num]) mp[num]=s; else
            mp[num]=min(mp[num],s);*/
        return;
    }
    bz[x]=true;
    dfs1(x+1);
    bz[x]=false;
    dfs1(x+1);
}
inline void dfs2(int x)
{
    if(x>m2)
    {
        long long num=0;
        int s=-1;
        for(int i=m1+1;i<=m2;i++)
            if(bz[i]) num^=f[i],s++;
        int k=hash(p[n]-1-num);
        if(h[k]) ans=min(ans,g[k]+s);
        //if(mp[p[n]-1-num]) ans=min(ans,mp[p[n]-1-num]+s);
        return;
    }
    bz[x]=true;
    dfs2(x+1);
    bz[x]=false;
    dfs2(x+1);
}
int main()
{
    freopen("1!.in","r",stdin);
    n=read(),m=read();
    for(int i=p[0]=1;i<=n;i++) p[i]=p[i-1]<<1;
    for(int i=1;i<=m;i++)
    {
        int x=read(),y=read();
        a[x][++a[x][0]]=y;
        a[y][++a[y][0]]=x;
    }
    for(int i=1;i<=n;i++)
    {
        f[i]=p[i-1];
        for(int j=1;j<=a[i][0];j++) f[i]+=p[a[i][j]-1];
    }
    m1=min(N>>1,m2=n);
    //mp[0]=1;
    dfs1(1);
    dfs2(m1+1);
    printf("%d",ans);
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值