UVA 10318 Security Panel(DFS剪枝 + 状压 + 思维)题解
题意:给一个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 + 10; const int MOD = 1e9 + 7; const int INF = 0x3f3f3f3f; int r, c; char s[10][10]; ll change[30], ansStep[30], step[30], vis[10][10], lit, ansCnt, cnt, OK; void init(int x, int y){ int pos = (x - 1) * c + y; change[pos] = 0; for(int i = 1; i <= 3; i++){ for(int j = 1; j <= 3; j++){ if(s[i][j] == '.') continue; int xx = x + i - 2, yy = y + j - 2; if(xx < 1 || xx > r || yy < 1 || yy > c) continue; ll p = (xx - 1) * c + yy; change[pos] += (1LL << p); } } } void dfs(int pos){ if(pos > r * c) return; int x = pos / c, y = pos % c; if(x >= 3){ for(int i = 1; i <= c; i++){ int pos2 = (x - 3) * c + i; if(!(lit & (1LL << pos2))) return; } } lit ^= change[pos]; vis[x][y] = 1; step[cnt++] = pos; if(lit == OK){ if(cnt < ansCnt){ for(int i = 0; i < cnt; i++) ansStep[i] = step[i]; ansCnt = cnt; lit ^= change[pos]; vis[x][y] = 0; cnt--; return; } } dfs(pos + 1); lit ^= change[pos]; vis[x][y] = 0; cnt--; dfs(pos + 1); } int main(){ int ca = 1; while(~scanf("%d%d", &r, &c) && r + c){ OK = 0; for(int i = 1; i <= 3; i++) scanf("%s", s[i] + 1); for(int i = 1; i <= r; i++){ for(int j = 1; j <= c; j++){ init(i, j); OK += (1LL << ((i - 1) * c + j)); } } ansCnt = 100; memset(vis, 0, sizeof(vis)); dfs(1); printf("Case #%d\n", ca++); if(ansCnt == 100) printf("Impossible.\n"); else{ for(int i = 0; i < ansCnt; i++){ if(i != 0) printf(" "); printf("%d", ansStep[i]); } printf("\n"); } } return 0; }