翻转游戏是在一个 4x4 的方形场地上进行的,在其 16 个方格中的每一个方格上都放置有两个面的棋子。
每个棋子的一面是白色的,另一面是黑色的,每个都是黑色或白色的一面朝上。
每轮你翻转 3 到 5 块,从而将它们上侧的颜色从黑色变为白色,反之亦然。
每轮都根据以下规则选择要翻转的棋子:
1.选择 16 个中的任何一个。
2.将选定的棋子以及所有相邻的棋子翻转到所选棋子的左侧、右侧、顶部和底部(如果有的话)。
例如:
bwwb
bbwb
bwwb
bwww
问题:
现在求将棋子翻转成同一种颜色的最小次数,如果不可能输出“Impossible”
首先,看到这道题就可以确定两件事:
大概的思路就是(非常暴力)
从小到大枚举翻转的次数n(n属于[0,15]),对于每个n,都对每个点都做翻转,然后判断结果如果不合适再回溯,直到所有点都翻转过了。如果有合适的,那必然就是最小的翻转次数,直接输出。如果n枚举完了都没有,就输出“Impossible”。
伪代码:
for(n in 0->16):
dfs(n, 0 ,0)
dfs(n,i,j)->bool:
if(n==0) return check();// 判断当前行不行
for(i->N):
for(j->M):
flip(i,j)
if(dfs(n-1,i,j)) return true
filp(i,j)// 回溯
j=0// 新行
return false
不难发现棋盘大小是固定的,我们可以用一个bool型的二维数组来存它,也可以用一个int(32位)[]
或者char(8)[]
来存储
(例如,如bwwb,将b存为1,w存0,用一个char来存就是0000 1001 他的int值(前面补上0)是9。
//每一行压缩为一个数字
char field[5];//下标从1开始
确定了存储的数据结构我们就能写出读入的代码
// read 下标从1开始
for (int i = 1; i <= 4; i++) {
for (int j = 1; j <= 4; j++) {
field[i] <<= 1;
if (getchar() == 'b')
field[i] |= 1; //与1相或,将最后一位变成1
}
getchar(); //读取换行符号
}
因为棋盘一行的大小固定4为所以用4位二进制数与一行的代码做亦或就可以了
这里的^(亦或)
和1亦或相当于取反
1^1=0
0^1=1
翻转的代码:
// {1000, 0100, 0010, 0001},{1100, 1110, 0111, 0011}
char p[][4] = { { 8, 4, 2, 1 }, { 12, 14, 7, 3 } };
void flip(int i, int j) { //对(i, j)位置的棋子进行翻转
--j;// 因为j是从0开始的
field[i - 1] ^= p[0][j];// 从1开始不用考虑越界
field[i] ^= p[1][j];
field[i + 1] ^= p[0][j];
}
这里用了一个数组p存放反转一行的几种可能。
还有判断是否满足条件的代码:
bool check() { // 0或15: 全0或全1
return (field[1] == 0 || field[4] == 15) &&
field[1] == field[2] &&
field[1] == field[3] &&
field[1] == field[4];
}
#include
#include
using namespace std;
//每一行压缩为一个数字
char field[5];
// 1000, 0100, 0010, 0001
// 1100, 1110, 0111, 0011
char p[][4] = { { 8, 4, 2, 1 }, { 12, 14, 7, 3 } };
void flip(int i, int j) { //对(i, j)位置的棋子进行翻转
--j;
field[i - 1] ^= p[0][j];
field[i] ^= p[1][j];
field[i + 1] ^= p[0][j];
}
bool check() { // 0或15: 全0或全1
return (field[1] == 0 || field[4] == 15) &&
field[1] == field[2] &&
field[1] == field[3] &&
field[1] == field[4];
}
bool dfs(int n, int i, int j) { //翻 n 个棋子
if (n == 0)
return check(); //判断是否符合条件
++j; //下一列
if (j > 4) {
++i;
j = 1;
}
if (i > 4) //如果超过棋盘大小则不可能
return false;
for (; i <= 4; ++i) {
for (; j <= 4; ++j) {
flip(i, j); //翻转
if (dfs(n - 1, i, j)) //在当前基础上递归
return true;
flip(i, j); //回溯
}
j = 1; // 新一行
}
return false;
}
int main() {
// read 下标从1开始
for (int i = 1; i <= 4; i++) {
for (int j = 1; j <= 4; j++) {
field[i] <<= 1;
if (getchar() == 'b') {
field[i] |= 1; //与1相或,于是就只改变最后一位
}
}
getchar(); //读取换行符号
}
// solve
for (int i = 0; i <= 16; i++) {
if (dfs(i, 1, 0)) {
cout << i <<endl;
exit(0); // 找到了直接退出
}
}
cout << "Impossible" << '\n';
return 0;
}
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Scanner;
public class POJ1753 {
private static final int N = 4, M = 4;
static final int[] field = new int[N];
public static void main(String[] args) throws IOException {
// Scanner in = new Scanner(Path.of("a.txt"), StandardCharsets.UTF_8);
Scanner in = new Scanner(System.in);
for (int i = 0; i < M; ++i) {
char[] c = in.next().toCharArray();
for (int j = 0; j < M; ++j) {
field[i] <<= 1;
if (c[j] == 'b')
++field[i];// 将最后一位变成1
}
}
int i = 0;
for (; i < 16; ++i)// 枚举翻转次数
if (dfs(i, 0, -1)) {
System.out.println(i);
break;
}
if (i == 16)
System.out.println("Impossible");
}
// 翻 n 个棋子
private static boolean dfs(int n, int i, int j) {
if (n == 0)
return check();
++j;// 下一列,没有会超时
if (j == 4) {
++i;
j = 0;
}
if (i == 4) // 如果超过棋盘大小则不可能
return false;
for (; i < N; ++i) {
for (; j < M; ++j) {
flip(i, j);// 翻转
if (dfs(n - 1, i, j))// 在当前基础上递归
return true;
flip(i, j);// 回溯
}
j = 0;// 新一行
}
return false;
}
// 1000, 0100, 0010, 0001
// 1100, 1110, 0111, 0011
private final static int[][] P = { { 8, 4, 2, 1 }, { 12, 14, 7, 3 } };
// 对(i, j)位置的棋子进行翻转
private static void flip(int i, int j) {
if (i > 0)
field[i - 1] ^= P[0][j];
field[i] ^= P[1][j];
if (i < N - 1)
field[i + 1] ^= P[0][j];
}
// 0或15: 全0或全1
private static boolean check() {
return (field[0] == 0 || field[3] == 15) &&
field[0] == field[1] &&
field[0] == field[2] &&
field[0] == field[3];
}
}