这段时间要沉迷刷题一段时间了,就让CSDN陪我一起吧!
一、题目大意
这道题目的大致意思就是给定一个4×4的矩阵,其中有白棋和黑棋,最终的目的是要把他们都变成白棋或都变成黑棋,所能进行的操作只有翻转,翻转的条件如下:
- 在每次操作的时候可以翻转一个16个棋子中的一个
- 每次翻转一个棋子的时候,必须连同其上下左右的棋子一起翻转
问最终,最少经过多少次翻转,可以达到把全部棋变为黑棋或把全部棋子变为白棋的目的?
二、题目思路以及AC代码
这不是我第一次做这道题了,甚至不是第二次。但这回自己思考的很快速,也是思考的比较有梯度的把这道题做了出来。
首先,考虑因为其只有黑棋和白棋两种情况,所以简单起见,我们需要采用位压缩对其进行存储,也就是字母’b’视为二进制1,字母’w’视为二进制0,这样所给的样例就可以存储为二进制1001 1101 1001 1000,则可以直接用一个int来存储。
接下来,考虑如何进行翻转操作。因为我们将4×4的矩阵用一个整数来存储,所以其棋子的翻转,也就是这个整数的某个二进制位的翻转,对于二进制位的翻转,可以采用异或来进行。这样我们就可以提前构建一个长度为16的数组state,当需要翻转棋子i的时候,就可以直接另当前的地图异或state[i]就可以了。
为了更加模块化的写我们的代码,我们还可以把检验当前地图是否满足最终目的(全0或全1)作为一个函数check(),这个函数也就很简单了,直接传入存储地图的整数,如果其值为0或者65535,则满足要求,否则不满足。
然后就是考虑如何翻转棋子了,我这里使用的是dfs,很容易想到,我们只需要对其进行暴力遍历就可以了。答案无非就是0 - 16中的一个(因为如果翻转17次,则某一个棋子会被翻转两次,也就又翻回来了,没有意义),所以我们遍历这16个数,每次尝试各种翻转方式,如果发现最终可以翻转到全0或全1,则输出对应的翻转次数即可。
我一开始也没想那么多,就直接暴力dfs了,然后发现TLE了。我咧嘴一笑,呵呵,直接记忆化搜索!果然,运用了记忆化搜索之后,157ms,Accepted。
下面给出AC代码:
#include <iostream>
using namespace std;
int state[17] = { 0, 51200, 58368, 29184, 12544, 35968, 20032, 10016, 4880, 2248, 1252, 626, 305, 140, 78, 39, 19 };
bool vis[17];
int map; // 利用位压缩
int dp[65536][17];
void init() {
for (int i = 0; i < 65536; i++) {
for (int j = 0; j < 17; j++) {
dp[i][j] = -1;
}
}
}
// 检查是否为全黑或全白 即全1或全0
bool check(int i) {
if (i == 0 || i == 65535)
return true;
return false;
}
// 翻转
int flip(int m, int i) {
return m ^ state[i];
}
// dfs
// m为当前地图 i为剩余翻转次数
bool dfs(int m, int k) {
if (k == 0)
return check(m);
if (dp[m][k] != -1) return dp[m][k];
for (int i = 1; i <= 16; i++) {
if (vis[i]) continue;
vis[i] = true;
int tmpm = flip(m, i);
if (dp[tmpm][k - 1] = dfs(flip(m, i), k - 1))
return true;
vis[i] = false;
}
return false;
}
int main()
{
init();
char c;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
cin >> c;
if (c == 'b') {
map = map | 1;
}
map <<= 1;
}
}
map >>= 1;
int ans = -1;
for (int i = 0; i <= 16; i++) {
memset(vis, false, sizeof(vis));
if (dfs(map, i)) {
ans = i;
break;
}
}
if (ans != -1)
cout << ans << endl;
else
cout << "Impossible" << endl;
return 0;
}
如果有问题,欢迎大家指正!!!