【题目链接】
OpenJudge NOI 2.1 755:Flip Game
【题目翻译】
翻转游戏
描述
翻转游戏是在一个矩形的4*4区域玩的游戏,16个方格中每格都放一个双面棋子。每个棋子的一面是白色另一面是黑色,每个棋子只能是白色面朝上或黑色面朝上。每轮你翻转3到5个棋子,可以把这些棋子朝上一面的颜色从黑色变成白色或反过来。
每轮根据一下规则选择要被翻转的棋子:
- 选择16个棋子中的任意一个
- 翻转被选中的棋子和所有与它上下左右相邻的棋子(如果存在)
考虑以下位置作为例子:
bwbw
wwww
bbwb
bwwb
这里b表示棋子黑色一面朝上,w表示棋子白色一面朝上。如果我们选择翻转第3行的第1个棋子(这个选择在图中展示了),那么盘面会变成:
bwbw
bwww
wwwb
wwwb
游戏的目标是把所有的棋子翻转成全部棋子白色朝上,或全部棋子黑色朝上。你需要写一个程序来寻找为了能够达到目标需要经过的最少轮数。
输入
输入包含4行,每行有4个字符。每个w和b表示游戏棋盘中的一个位置的棋的颜色。
输出
写入输出文件一个整数:从当前盘面到达成目标需要的最少轮数。如果一开始就达成了目标,写入0。如果不可能达成目标,写入"Impossible"(不包含引号)
样例输入
bwwb
bbwb
bwwb
bwww
样例输出
4
来源
Northeastern Europe 2000
【题目考点】
1. 枚举
2. 二进制
【解题思路】
该题就是点灯问题。
与OpenJudge NOI 2.1 1813:熄灯问题思路相同。
枚举对第一行的所有翻转方法。用1表示某处翻转,用0表示某处不翻转,那么对第1行有0000~1111共16种翻转方法。该过程可以通过枚举0~15的整数,而后将其在二进制下进行数位分离,得到对第1行的翻转方案。或者也可以通过搜索来完成。
第一行的翻转方案确定后,第1行的黑白情况就确定了。接下来分两个目标:
- 目标1:把所有棋子都翻成白色
- 目标2:把所有棋子都翻成黑色
以目标1为例,想要把所有棋子都翻成白色,第1行的翻转方案已经确定了,第1行翻转后,还剩余一些黑色棋子。那么这些棋子必须通过对第2行同列棋子进行翻转,才能将第1行的黑色棋子都变为白色。因此第2行的翻转方案确定了。
第2行翻转后,第2行的黑色棋子必须通过对第3行的棋子进行翻转后才能变成白色。因此第3行的棋子也确定了。
以此类推,每行的翻转情况都确定了,如果最后一行在翻转后,最后一行不都是白色,那么这一躺对第1行的翻转是无效的,去看下一种对第1行的翻转。
如果最后一行都变为白色,那么得到一个成功翻转的方案,更新成功翻转的最少步数。
而后用同样的方法尝试把所有棋子都翻成黑色,如果成功,则更新成功翻转的最少步数。
最后输出成功翻转的最少步数。
【题解代码】
解法1:枚举
#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
int dir[4][2] = {
{
0,1},{
0,-1},{
1,0},{
-1,0}};
int mp_ori[5][5], mp[5][5];//mp_ori:原始棋盘, mp:临时棋盘 mp[i][j]为1表示(i,j)是黑色棋子,为0表示(i,j)是白色棋子 mp_t
void flip(int sx, int sy)//在(sx,sy)位置翻转棋
{
mp[sx][sy] = !mp[sx][sy];
for(int i = 0; i < 4; ++i)
{
int x = sx + dir[i][0], y = sy + dir[i][1];
if(x >= 1 && x <= 4 && y >= 1 && y <= 4)
mp[x][y] = !mp[x][y];//翻转(x,y)位置的棋子,黑变白,白变黑
}
}
//获取把整个盘面都翻转成color颜色需要的翻转次数。第一行的翻转方案由n的二进制数字来表示,如果不能成功翻转,返回-1
//例:如果n是10,二进制下为1010,那么(1,1), (1,3)位置需要翻转
int getFlipNum(int color, int n)
{
memcpy(mp, mp_ori, sizeof(mp_ori));//重置临时棋盘mp为原始棋盘mp_ori
int a = n, flipNum = 0;//flipNum:翻转棋子的次数
for(int j = 4; j >= 1; --j)
{
//看(1,j)位置是否需要翻转
if(a % 2 == 1)//如果这一位是1,则需要翻转
{
flip(1, j);
flipNum++;
}
a /= 2;
}
for(int i = 2; i <= 4; ++i)
{
for(int j = 1; j <= 4; ++j)
{
if(mp[i-1][j] != color)//如果(i-1,j)不是目标颜色,那么必须翻转(i,j)
{
flip(i, j);
flipNum++;
}
}
}
for(int j = 1; j <= 4; ++j)//如果第4行不都是目标颜色color,那么没有把整个盘面都翻成color
if(mp[4][j] != color)
return -1;
return flipNum;
}
int main()
{
int minFlipNum = INF;//成功把所有棋子都翻成黑色或白色的最少翻转次数
char c;
for(int i = 1; i <= 4; ++i)
for(int j = 1; j <= 4; ++j)
{
cin >> c;
mp_ori[i][j] = c == 'b' ? 1 : 0;//如果是黑棋子,mp_ori[i][j]是1,如果是白棋子,mp_ori[i][j]是0
}
for(int n = 0; n < 16; ++n)//n变为二进制数字后,就是对第1行的翻转操作,1为
{
int flipNum_white = getFlipNum(0, n), flipNum_black = getFlipNum(1, n);//将当前棋盘所有棋子翻转成白色、黑色需要的翻转次数
if(flipNum_white != -1)
minFlipNum = min(minFlipNum, flipNum_white);
if(flipNum_black != -1)
minFlipNum = min(minFlipNum, flipNum_black);
}
if(minFlipNum == INF)
cout << "Impossible";
else
cout << minFlipNum;
return 0;
}