【ZJOJ1321】灯
题目
贝希和她的闺密们在她们的牛棚中玩游戏。但是天不从人愿,突然,牛棚的电源跳闸了,所有的灯都被关闭了。贝希是一个很胆小的女生,在伸手不见拇指的无尽的黑暗中,她感到惊恐,痛苦与绝望。她希望您能够帮帮她,把所有的灯都给重新开起来!她才能继续快乐地跟她的闺密们继续玩游戏!
牛棚中一共有N(1 <= N <= 35)盏灯,编号为1到N。这些灯被置于一个非常复杂的网络之中。有M(1 <= M <= 595)条很神奇的无向边,每条边连接两盏灯。
每盏灯上面都带有一个开关。当按下某一盏灯的开关的时候,这盏灯本身,还有所有有边连向这盏灯的灯的状态都会被改变。状态改变指的是:当一盏灯是开着的时候,这盏灯被关掉;当一盏灯是关着的时候,这盏灯被打开。
问最少要按下多少个开关,才能把所有的灯都给重新打开。
数据保证至少有一种按开关的方案,使得所有的灯都被重新打开。
分析
发现,当我们打开一个开关,就会取反一些灯的状态:0/1;
对于第i个开关,就可以有一个对其他灯是否有影响的n位的二进制数。
一开始,所有灯的状态为0,
现在要让他全部变为1,即\(2^n-1\)。
题目就转化为,n个二进制数,如何取最少的数使他们的异或中为\(2^n-1\)。
观察数据范围\(n\le35\)
直接枚举每个数选不选显然会超时。
考虑,我们每次只枚举n/2个,
先枚举前n/2个,将所有组合出的异或值扔进一个hash,或C++的map中(后者有点慢,为了方便,我打了map)
再枚举剩下的,所有组合出的异或值与\(2^n-1\)异或一下,将异或出来的值看一下map中有没有,如果有,则与ans取min。
#include <cmath>
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <map>
const int maxlongint=2147483647;
const int mo=1000000007;
const long long N=45;
using namespace std;
int n,m,ans,tot;
long long a[N],mi[N];
map<long long,int> arr;
void dg(int y,long long v,int dis)
{
if(dis>ans) return;
if(v==mi[n]-1) ans=min(ans,dis);
else
{
if(arr[v]==0) arr[v]=dis;
else arr[v]=min(arr[v],dis);
}
if(y>n) return;
for(int i=y;i<=n;i++) dg(i+1,v^a[i],dis+1);
}
void dg1(int y,long long v,int dis)
{
if(dis>ans) return;
if(v==mi[n]-1) ans=min(ans,dis);
else
{
tot++;
if(arr[(mi[n]-1)^v]!=0) ans=min(arr[(mi[n]-1)^v]+dis,ans);
}
if(y>n/2) return;
for(int i=y;i<=n/2;i++) dg1(i+1,v^a[i],dis+1);
}
int main()
{
scanf("%d%d",&n,&m);
mi[0]=1;
for(int i=1;i<=36;i++) mi[i]=mi[i-1]*2,a[i]=mi[i-1];
for(int i=1,x,y;i<=m;i++)
{
scanf("%d%d",&x,&y);
a[x]+=mi[y-1];
a[y]+=mi[x-1];
}
ans=maxlongint;
dg(n/2+1,0,0);
arr[0]=0;
dg1(1,0,0);
printf("%d",ans);
}