一、题目大意
本题目其实是关于一个点灯游戏的玩法,相信很多人多玩过这个游戏,每次电亮一盏灯,这盏灯周围的4个灯也都会一起点亮,本题目是要求出最少的操作次数,电亮所有的灯,并把电亮的灯输出在答案里。
二、解题思路
我们能够直到,同样一盏灯电亮两次,等于没点!所以我们记录一个数组f[i][j],用来放置每个坐标是否被电亮过。
然后我们只需要计算f[i][j]+f[i-1][j]+f[i+1][j]+f[i][j-1]+f[i][j+1]的和sum,如果sum是奇数,那么该位置与最开始输入的位置的颜色相反,否则颜色相同。
那么这就变成了一个反转问题,我们利用二进制,从0000,到1111(N=4时)枚举第一行的所有点灯情况,然后从第2行开始到M行循环所有坐标,判断如果它上一行灯是灭着的,就电亮这盏灯,否则就continue
然后到最后一行判断下,如果最后一行有灯是灭着的,那么这个情况无解(因为最后一行再改动一定会影响到上面的,那么其实不可能全亮了),遍历第一行的下一种情况,如果最后一行所有灯全亮,那么把当前f[i][j]数组求和,如果小于记录的ans,那么就保存下当前的结果。
最后判断下,如果ans从未被修改,那么输出IMPOSSIBLE,否则输出记录的结果
复杂性是 (2^N)*(MN),是可行的。
三、代码
#include <iostream>
using namespace std;
int towPow[27], revCnt[17][17], M, N, ansRevCnt, count, ans[17][17];
bool white[17][17];
void initTwoPow()
{
towPow[0] = 1;
for (int i = 1; i <= 20; i++)
{
towPow[i] = towPow[i - 1] * 2;
}
}
void input()
{
scanf("%d%d", &M, &N);
int x;
for (int i = 1; i <= M; i++)
{
for (int j = 1; j <= N; j++)
{
scanf("%d", &x);
if (x == 0)
{
white[i][j] = true;
}
else
{
white[i][j] = false;
}
}
}
ansRevCnt = 0x3f3f3f3f;
}
void flushRevCnt()
{
for (int i = 1; i <= M; i++)
{
for (int j = 1; j <= N; j++)
{
revCnt[i][j] = 0;
}
}
}
// 计算i,j坐标的一圈五个位置的结果
int calcRevCntAroundIJ(int i, int j)
{
int result = revCnt[i][j];
if (i > 1)
{
result += revCnt[i - 1][j];
}
if (j > 1)
{
result += revCnt[i][j - 1];
}
if (j < N)
{
result += revCnt[i][j + 1];
}
if (i < M)
{
result += revCnt[i + 1][j];
}
return result;
}
// 判断最后一行是否全是白色
bool judgeLastLine()
{
for (int col = 1; col <= N; col++)
{
int currentRevCnt = calcRevCntAroundIJ(M, col);
bool isWhite = white[M][col];
if (currentRevCnt % 2 != 0)
{
isWhite = !white[M][col];
}
if (!isWhite)
{
return false;
}
}
return true;
}
void saveAns()
{
int currentRevCnt = 0;
for (int i = 1; i <= M; i++)
{
for (int j = 1; j <= N; j++)
{
currentRevCnt += revCnt[i][j];
}
}
if (currentRevCnt >= ansRevCnt)
{
return;
}
ansRevCnt = currentRevCnt;
for (int i = 1; i <= M; i++)
{
for (int j = 1; j <= N; j++)
{
ans[i][j] = revCnt[i][j];
}
}
}
void solve()
{
for (int i = 2; i <= M; i++)
{
for (int j = 1; j <= N; j++)
{
int beforeLineRevCnt = calcRevCntAroundIJ(i - 1, j);
bool beforeLineIsWhite = white[i - 1][j];
if (beforeLineRevCnt % 2 != 0)
{
beforeLineIsWhite = !white[i - 1][j];
}
if (!beforeLineIsWhite)
{
revCnt[i][j] = 1;
}
}
}
if (judgeLastLine())
{
saveAns();
}
}
// 利用二进制,来枚举第一行翻与不翻的所有可能
void handleFirstLineByBin(int numBin)
{
for (int j = N - 1; j >= 0; j--)
{
int x = numBin & towPow[j];
x = x >> j;
if (x == 1)
{
revCnt[1][N - j] = 1;
}
else
{
revCnt[1][N - j] = 0;
}
}
}
void outputAns()
{
if (ansRevCnt == 0x3f3f3f3f)
{
printf("IMPOSSIBLE\n");
return;
}
for (int i = 1; i <= M; i++)
{
for (int j = 1; j <= N; j++)
{
if (j != N)
{
printf("%d ", ans[i][j]);
}
else
{
printf("%d\n", ans[i][j]);
}
}
}
}
int main()
{
initTwoPow();
input();
int base = 1048576, len = towPow[N];
for (int i = 0; i < len; i++)
{
flushRevCnt();
int numBin = base + i;
handleFirstLineByBin(numBin);
solve();
}
outputAns();
return 0;
}