费解的开关(模拟+二进制+思维)
题目链接:https://ac.nowcoder.com/acm/contest/998/D
题意:就是给出t组询问数据,然后每一组数据给出一个5*5的矩阵,矩阵元素是由0、1构成,并且每一次按动一个元素会导致其上下左右与自身共计5个位置的元素发生改变,即0<->1,闲现在请问你能否再6步之内将所有的0都变为1。
思路:我们通过观察,发现既然每按动一个按钮都会导致周围五个按钮状态发生变化,不难想到判断第i排是否为0,若为0我们就通过按第i+1排的按钮来改变第i排按钮的状态,并且通过这样的操作,将第i排的状态就永远固定为1了,(因为下一次是判断第i+1排,通过改变第i+2排,而第i+2排的改变并不会影响到第i排按钮状态),最后通过判断最后一排是否全部为1来判断方案是否可行。
技巧:
1.通过二进制枚举第一排的操作可能,还是与之前的二进制思想相同,判断位数是否为1来进行操作。因为通过枚举二进制数,是可以枚举完所有的可能性的,因为比如有n个按钮,那么其操作方案数就是2n种,而区间[0,1 << n)刚好就是包含了所有可能性。
2.每一次枚举第一排的操作方案,都要先将初始情况保存,再进行完操作之后,再恢复初始状态,运用memcpy(a,b,sizeof(b))可以快速完成这一操作。
3.每一次按动开关进行操作,都需要对步数递增,最终通过最小步数判断答案是否符合要求。
做题代码技巧:先布局,再写具体的实现函数。
代码:
#include <bits/stdc++.h> using namespace std; const int INF = 10000005; char s[10][10]; int dix[5] = {0, -1, 1, 0, 0}; int diy[5] = {0, 0, 0, 1, -1}; void turn(int a, int b) { for(int i = 0; i < 5; i++) { int x = a + dix[i]; int y = b + diy[i]; if(x >= 0 && x < 5 && y >= 0 && y < 5) s[x][y] ^= 1; } } int work() { int ans = INF; for(int k = 0; k < 1 << 5; k++) { int res = 0; char backup[10][10]; memcpy(backup, s, sizeof(s)); for(int i = 0; i < 5; i++) if(k >> i & 1) { res ++; turn(0, i); } for(int i = 0; i < 4; i++) for(int j = 0; j < 5; j++) if(s[i][j] == '0') { turn(i + 1, j); res++; } bool is_ok = true; for(int i = 0; i < 5; i++) if(s[4][i] == '0') { is_ok = false; break; } if(is_ok) ans = min(ans, res); memcpy(s, backup, sizeof(s)); } return ans > 6 ? -1 : ans; } int main() { ios::sync_with_stdio(false); int t; cin >> t; while(t--) { for(int i = 0; i < 5; i++) cin >> s[i]; cout << work() << endl; } return 0; }