题意:
牛客网
在4*4的棋盘里有16个可黑可白的棋,每轮游戏可以选择将一个棋及其上下左右相邻的棋的颜色取反(最多五个最少三个),所有棋面颜色相同时游戏结束,求最少进行几轮游戏后游戏结束
思路:
对任意的一个棋子进行两次翻转操作是无意义的,负负得正,因此一个棋最多只翻一次,可以采取枚举
我们从上至下(当然你也可以换个方向),一行一行的将棋子翻到相同的颜色(比如说我们现在要把它们全部翻成白色),我们记0为黑,1为白。
假如前两行的棋子是0101,1111,那么为了让第一行的棋子全都变成1,我们需要对第二行第一个、第二行第三个,也就是对第一行的0下面的那个棋子进行反转。
这样翻转以后,只需要**再判断一下最后一行是否全是白色(游戏是否能被正确的结束)**即可
但是这样解题是依据第一行一开始的颜色来进行推算的,而我们又可以对第一行的棋进行翻转,所以我们需要列出第一行的所有翻转情况,并向下推算需要翻转的次数,找出最小的次数即可
代码:
*代码用二维数组会更好看一些,但这题我尝试了一下位运算
*下面给的代码里注释了display之类的代码,是debug时候用的
*代码写的又臭又长,但也写的比较仔细了,看懂应该不难 嘤嘤嘤
#include <iostream>
using namespace std;
/*返回map中第row行第column个棋的颜色*/
inline int getColor(int row, int column, int map) {
int gap = 16 - (((row - 1) * 4) + column); //位运算时左移右移的位数
return (map & (1 << gap)) >> gap;
}
//输出当前棋盘的图
inline void display(int map) {
cout << map << ":" << endl;
for (int i = 15; i >= 0; --i) {
cout << ((map >> i) & 1);
if (i % 4 == 0)cout << endl;
}
cout << endl;
}
//反转(row,column)上的棋
inline void reverse(int row, int column, int &map) {
int gap = 16 - (((row - 1) * 4) + column);
int reverse = 1 << gap;
//反转当前的棋
map = map ^ reverse;
//反转它下面的棋
if (row != 4)map ^= (reverse >> 4);
//反转它上面的棋
if (row != 1)map = map ^ (reverse << 4);
//反转它左面的棋
if (column != 1)map = map ^ (reverse << 1);
//反转它右面的棋
if (column != 4)map = map ^ (reverse >> 1);
}
/*
* 将棋盘中的黑色全部翻成白色
* 并返回最少翻转次数*/
int flipGame(int originMap) {
int minRes = 17;
/*
* 先将第一行的反转情况遍历,从0000-1111共有16种可能(0为不反转,1为反转)
* */
for (int i = 0; i < 16; ++i) {
int res = 0; //反的次数
int copyMap = originMap;
// cout<<"CopyMap:\n";
// display(copyMap);
//反转第一行
for (int j = 3; j >= 0; --j) {
if ((1 << j) & i) { //根据二进制i逐位判断是否需要反转
res++;
reverse(1, 4 - j, copyMap);
}
}
// cout<<"i="<<i<<" 遍历后的map:\n";
// display(copyMap);
//对剩下的棋进行反转
for (int row = 1; row <= 3; ++row) {
for (int column = 1; column <= 4; ++column) {
//如果当前的棋是黑色则反转它 下面 的那个棋(让当前的棋变成白色)
if (getColor(row, column, copyMap) == 0) {
res++;
reverse(row + 1, column, copyMap);
// display(copyMap);
}
}
}
//如果棋盘成功被全部翻成白色,记录翻的次数
if ((copyMap & 15) == 15) {
minRes = min(res, minRes);
}
// cout<<"---------------------------"<<endl;
}
return minRes;
}
int main() {
char temp; //工具人变量
int map = 0; //游戏棋盘,用二进制表示,0代表黑,1代表白
for (int i = 0; i < 16; ++i) {
cin >> temp;
if (temp == 'b')map = map << 1;
else if (temp == 'w')map = (map << 1) + 1;
}
int a, b;
a = flipGame(map);
// cout << a << endl;
//map中黑白反转,0代表白,1代表黑,再进行一次游戏并记录最小反转次数
map = map ^ ((1 << 16) - 1);
b = flipGame(map);
// cout << b << endl;
if (a == 17 && b == 17) {
cout << "Impossible" << endl;
return 0;
}
cout << min(a, b);
}
第52行 根据二进制 i 逐位判断是否需要反转 的补充说明:
对于第一行棋子,我们记翻转情况0为不翻转,1为翻转,那么for循环i=0到i<16就将i从0000遍历到1111,列举所有情况。
下面52行的for和下面的if:用for和左移,让0001,0010,0100,1000和i进行位与操作,如果得到的不是0,即代表第一行上那一个棋子需要进行翻转操作