假期集训Day2 T6(一道绝佳的双向DFS题)
题面
[题目描述]
牛棚里一共有 N 盏灯,电灯间有 M 条电线,其中第 i 条电线连接了第 A i 和 B i 盏灯,A i ̸= B i ,也不会有多条电线重复连接同一组灯。每盏灯上有个开关,开关会改变灯的状态——把不亮的灯点亮,或亮的灯变成不亮。不仅如此,由于电线的作用,按下某盏灯的开关后,和这盏灯直接相连的其他电 灯也会随之改变状态。 假设刚开始的时候,所有电灯都是关着的,请问按下哪些灯的开关,才能把所有的灯都点亮?输出按下开关的最少次数,保证解法一定存在。
输入
• 第一行:两个整数 N 和 M,1 ≤ N ≤ 35, 1 ≤ M ≤ 595
• 第二行到第 M + 1 行:第 i + 1 行有两个整数 A i 和 B i ,1 ≤ A i ,B i ≤ N
输出
单个整数:表示最少按下的开关次数
样例输入
5 6 1 2 1 3 4 2 3 4 2 5 5 3
样例输出
3
样例解释
按下第一盏灯,第四盏灯,第五盏灯的开关
解析
双 向 D F S
考虑用2进制表示每一种状态,第一盏灯为1<<1,第二盏灯为1<<2,第i盏灯为1<<i,这样我们可以表示出任何一种状态,用map储存每一种状态,当搜索到一种状态使得他和之前的某一种状态相加=灯全部亮的状态,就输出两者的步数和。特别注意,一个状态可能被多种方式搜索到,所以在用map储存的时候要取一个最小值,否则就会WA28-78分不等
#include <bits/stdc++.h>
using namespace std;
map<long long,long long> maps;
map<long long,bool> spam;
long long n,m,a[40][40],x,y,endd,s[40];
long long pow2(long long x){
long long res=1;
for(long long i=1;i<=x;i++) res*=2;
return res;
}
void dfs(long long x,long long y,long long z){
if(maps[y]==0) maps[y]=INT_MAX;
maps[y]=min(maps[y],z);
spam[y]=true;
if(x>17) return;
if(y==endd) {
cout<<maps[y];
exit(0);
}
if(x>n) return;
long long yy=y;
for(long long i=1;i<=n;i++){
if(a[x][i]==1||x==i){
if((yy>>i)&(1ll)) yy-=pow2(i);
else yy+=pow2(i);
}
}
dfs(x+1,y,z);
dfs(x+1,yy,z+1);
}
void dfs2(long long x,long long y,long long z){
if(maps[y]==0) maps[y]=INT_MAX;
maps[y]=min(maps[y],z);
if(x>36) return;
if(spam[endd-y]!=0) {
cout<<maps[y]+maps[endd-y];
exit(0);
}
if(x>n) return;
long long yy=y;
for(long long i=1;i<=n;i++){
if(a[x][i]==1||x==i)
{
if((yy>>i)&(1ll)) yy-=pow2(i);
else yy+=pow2(i);
}
}
dfs2(x+1,y,z);
dfs2(x+1,yy,z+1);
}
int main(){
scanf("%d%d",&n,&m);
for(long long i=1;i<=m;i++){
scanf("%d%d",&x,&y);
a[x][y]=1,a[y][x]=1;
}
endd=pow2(n+1)-2;
dfs(1,0,0);
dfs2(18,0,0);
}