黑白翻转棋¶
keywords: 枚举
题意¶
题目链接,黑白翻转棋 题意,一个棋盘,每按一个位置,上下左右自己,5个位置的颜色会翻转。请问最少按几次,可以将棋盘变成全部白色。这是一道题目的改编,原题是《费解的开关》,在奥数中也常见。与原题不同的是,降低了数据读入的难度,但是增大了数据强度,很难AC掉
思路1,30pts¶
逐行逐列,每一位可以按,或者不按。时间复杂度 �(2�∗�) 。
具体递归枚举子集。或者写dfs,选或者不选。
思路2,85pts¶
在费解的开关里,优化是先枚举第一行的所有翻转方案,因为第一行定下来后,剩下的就是一行一行捋着按就可以了。没有第二种选择。时间复杂度 �(2�∗�∗�) 。
因n, m <= 20,2^20 * 20 * 20 = 1024 * 1024 * 400 = 4e8,这个应该是上下浮动看人品。果然,本题的数据,最后三个点,n和m给的都很大。所以,考察的不仅仅是你会二进制表示方案,还要会优化起来。
#include <bits/stdc++.h>
using namespace std;
const int N = 25;
int ret = 0x3f3f3f3f;
int g[N][N], bak[N][N], n, m;
int dx[] = {0, 1, 0, -1, 0}, dy[] = {1, 0, -1, 0, 0};
void change(int x, int y){
for (int i = 0; i < 5; i++){
int a = x + dx[i], b = y + dy[i];
if (a < 0 || a >= n || b < 0 || b >= m) continue;
if (g[a][b] == 1) g[a][b] = 0;
else g[a][b] = 1;
}
}
bool check(){
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
if (g[i][j]) return false;
return true;
}
void backup(int a[][N], int b[][N]){
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
a[i][j] = b[i][j];
}
int main(){
cin >> n >> m;
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++) cin >> g[i][j];
backup(bak, g);
for (int mask = 0; mask < (1 << m); mask++){
backup(g, bak);
int cnt = 0;
for (int j = 0; j < m; j++)
if ((mask >> j) & 1){
change(0, j);
cnt++;
}
for (int i = 0; i < n - 1; i++){
for (int j = 0; j < m; j++)
if (g[i][j]){
change(i + 1, j);
cnt++;
}
}
if (check()) ret = min(ret, cnt);
}
if (ret == 0x3f3f3f3f) cout << "Impossible" << '\n';
else cout << ret << '\n';
return 0;
}
思路3,100pts¶
思路2会超时,很定就是那个 �(∗�∗�) 惹的祸,在代码中,是老老实实 ∗�∗� ,然后按一个格子的时候,是模拟5个点for一遍。在这个环节上,参考了[枚举第一行+位运算],代老师的这个思路,但我进行了改编。
代码整体框架和思路2一致,区别是用一个二维数组press[i][j]来维护每一个格子的翻转情况。通过格子上原有数字,异或上相关的所有格子的翻转方案(能影响到本格子的),得到本格子翻转之后的结果。最后,还是看最后一行是否都是0,来判断方案的合法性。
// 下标必须从1开始,否则很多细节越界
#include <bits/stdc++.h>
using namespace std;
const int N = 25;
int n, m, ret = 0x3f3f3f3f;
int g[N][N]; // g[i]表示第i行的棋盘,每一行用二进制数表示,用[m位-1位]作为有效位,第0位不用
int press[N][N]; // press[i]表示第i行的翻转方案,二进制数表示,0/1表示是否按了
int check(){
int cnt = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
cnt += press[i][j];
return cnt;
}
void print(){
for (int i = 1; i <= m; i++) cout << press[1][i];
puts("");
}
int main(){
cin >> n >> m;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) cin >> g[i][j];
for (int mask = 0; mask < (1 << m); mask++){
for (int j = 1; j <= m; j++) press[1][j] = mask >> (j-1) & 1;
for (int i = 1; i <= n - 1; i++)
for (int j = 1; j <= m; j++){
press[i + 1][j] = g[i][j] ^ press[i][j] ^ press[i][j - 1] ^ press[i][j + 1];
if (i > 1) press[i + 1][j] ^= press[i - 1][j];
}
bool flag = false;
for (int j = 1; j <= m; j++){
int t = g[n][j] ^ press[n][j] ^ press[n][j - 1] ^ press[n][j + 1];
if (n > 1) t ^= press[n - 1][j];
if (t){
flag = true;
break;
}
}
if (flag) continue;
ret = min(ret, check());
}
if (ret == 0x3f3f3f3f) cout << "Impossible" << '\n';
else cout << ret << '\n';
return 0;
}
思路4,100pts¶
参考[枚举第一行+位运算],我觉得这个掩码的方法很好,我之前没用过,翻写一遍。还有把每一行的翻转直接维护到一个二进制数当中,从而省去了枚举行的过程,去掉了 �(∗�) ,这提高了很多速度
#include <bits/stdc++.h>
using namespace std;
const int N = 25;
int n, m, ret = 0x3f3f3f3f;
int g[N]; // g[i]表示第i行的棋盘,每一行用二进制数表示,用[m位-1位]作为有效位,第0位不用
int press[N]; // press[i]表示第i行的翻转方案,二进制数表示,0/1表示是否按了
int check(){
int cnt = 0;
for (int i = 1; i <= n; i++)
while (press[i]){
press[i] = press[i] & (press[i] - 1);
cnt++;
}
return cnt;
}
int main(){
cin >> n >> m;
for (int i = 1; i <= n; i++){
int x;
for (int j = 1; j <= m; j++){
cin >> x;
g[i] <<= 1;
g[i] |= x;
}
g[i] <<= 1; // 二进制的[m...1位]用来表示状态
}
int len = ((1 << m) - 1) << 1; // 11111111111110, m个1,用来控制m个1的
for (int mask = 0; mask < (1 << m); mask++){
press[1] = mask << 1; // 第一行的翻转方案
for (int i = 2; i <= n; i++)
press[i] = len & (press[i - 2] ^ press[i - 1] ^ (press[i - 1] >> 1) ^ (press[i - 1] << 1) ^ g[i - 1]);
// 看最后一行是否全是0
// 最后一行非0,就continue
int t = len & (press[n - 1] ^ press[n] ^ (press[n] >> 1) ^ (press[n] << 1) ^ g[n]);
if (t) continue;
ret = min(ret, check());
}
if (ret == 0x3f3f3f3f) cout << "Impossible" << '\n';
else cout << ret << '\n';
return 0;
}
总结¶
- 测试数据中应该没有n=1, m=1的数据,这个数据一出,应该可以hack掉现有的很多代码
- 这道题目也学到了很多,所以写一篇文章记录一下
- 看了代老师的代码,我本想用 bitset 操作一翻,结果以失败告终,不如使用二进制数可行