UVA 10318 Security Panel(DFS剪枝 + 状压 + 思维)题解

时间:2024-09-30 13:06:50

题意:给一个r*c的矩阵开关(初始全打开的),每次按下一个开关都会改变3*3范围内的有*的地方的状态,问你最少几步能让开关全闭上,按升序输出按哪些按钮

思路:每个按钮至多按一下,按按钮的顺序和结果无关。我们把当前矩阵的开关状态状压到 long long,并且预处理按下每个按钮的变化,这样可以直接异或得到新的状态。这里还需要剪枝,因为顺序无关,我们直接从1按到r*c,那么如果我们按到第k行,现在就已经影响不到k-2行了,所以k-2行如果有开关没闭上就return。

代码:

#include<set>
#include<map>
#include<stack>
#include<cmath>
#include<queue>
#include<vector>
#include<string>
#include<cstdio>
#include<cstring>
#include<sstream>
#include<iostream>
#include<algorithm>
typedef long long ll;
using namespace std;
const int maxn = 1e5 + ;
const int MOD = 1e9 + ;
const int INF = 0x3f3f3f3f;
int r, c;
char s[][];
ll change[], ansStep[], step[], vis[][], lit, ansCnt, cnt, OK;
void init(int x, int y){
int pos = (x - ) * c + y;
change[pos] = ;
for(int i = ; i <= ; i++){
for(int j = ; j <= ; j++){
if(s[i][j] == '.') continue;
int xx = x + i - , yy = y + j - ;
if(xx < || xx > r || yy < || yy > c) continue;
ll p = (xx - ) * c + yy;
change[pos] += (1LL << p);
}
}
}
void dfs(int pos){
if(pos > r * c) return;
int x = pos / c, y = pos % c;
if(x >= ){
for(int i = ; i <= c; i++){
int pos2 = (x - ) * c + i;
if(!(lit & (1LL << pos2))) return;
}
}
lit ^= change[pos];
vis[x][y] = ;
step[cnt++] = pos;
if(lit == OK){
if(cnt < ansCnt){
for(int i = ; i < cnt; i++)
ansStep[i] = step[i];
ansCnt = cnt;
lit ^= change[pos];
vis[x][y] = ;
cnt--;
return;
}
}
dfs(pos + );
lit ^= change[pos];
vis[x][y] = ;
cnt--;
dfs(pos + );
}
int main(){
int ca = ;
while(~scanf("%d%d", &r, &c) && r + c){
OK = ;
for(int i = ; i <= ; i++) scanf("%s", s[i] + );
for(int i = ; i <= r; i++){
for(int j = ; j <= c; j++){
init(i, j);
OK += (1LL << ((i - ) * c + j));
}
}
ansCnt = ;
memset(vis, , sizeof(vis));
dfs();
printf("Case #%d\n", ca++);
if(ansCnt == ) printf("Impossible.\n");
else{
for(int i = ; i < ansCnt; i++){
if(i != ) printf(" ");
printf("%d", ansStep[i]);
}
printf("\n");
}
}
return ;
}