本题目其实是关于一个点灯游戏的玩法,相信很多人多玩过这个游戏,每次电亮一盏灯,这盏灯周围的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
- 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;
- }