USTCOJ 1240,黑屋:http://acm.ustc.edu.cn/ustcoj/problem.php?id=1240
該題采用暴力枚舉的方式求出關燈所需的最少步數。其中press數組标記了了在每一次嘗試關閉所有燈時需要按下的按鈕。
程式将原有的2^(m*n)中可能的按按鈕操作縮減為了2^m次方種按按鈕操作。大大縮減了搜尋空間。對于每一種按按鈕操作,均調用turnOffLight函數嘗試關燈,若關燈成功,傳回true,不能将所有等關閉,則傳回false。turnOffLight函數執行完後,可通過press數組讀取本次關燈所需按下的按鈕。
該解法的加速增強版請參考:http://blog.csdn.net/l03071344/article/details/8816508
/***********
*原作者:liujianhua
*修改注釋:lance
***********/
#include <cstdio>
#include <cstring>
#define MAXLEN 100
#define MAXWID 15
//數組開大一些,省去邊界判斷
int puzzle[MAXLEN + 5][MAXWID + 5];
char press[MAXLEN + 5][MAXWID + 5];
int n, m;
bool turnOffLight()
{
int c, r;
for(r = 1; r < n; ++r)
{
for(c = 1; c < m + 1; ++c)
press[r + 1][c] = (puzzle[r][c] + press[r][c] + press[r - 1][c] + press[r][c - 1] + press[r][c + 1]) % 2;
}
for(c = 1; c < m + 1; ++c)
{
if((press[n][c - 1] + press[n][c] + press[n][c + 1] + press[n - 1][c]) % 2 != puzzle[n][c])
return false;
}
return true;
}
//将計數部分剝離為一個函數
int count_step()
{
int r, c, sum = 0;
for(r = 1; r <= n; r++)
{
for(c = 1; c <= m; ++c)
{
if(press[r][c] == 1)
++sum;
}
}
return sum;
}
int enumate()
{
int minsteps = -1;
int c = 0;
while (1)
{
//每次while循環guess函數總會執行
//從press[1]全0開始,即從不管第一行任何燈開始
if (turnOffLight())
{
int step = count_step();
if (minsteps == -1 || minsteps > step)
minsteps = step;
}
//對press[1]數組進行二進制加法,枚舉所有可能的關燈方式
press[1][1]++;
c = 1;
while (press[1][c] > 1)
{
press[1][c++] = 0;
press[1][c]++; //進位
}
//通過判斷進位是否已經到c,來确定是否已經枚舉完2^m種可能
if (c == m + 1)
break;
}
return minsteps;
}
main()
{
freopen("1240.in", "r", stdin);
freopen("1240.out", "w", stdout);
while((scanf("%d %d", &n, &m)) != EOF)
{
//需要對press數組初始化,清除上一輪對press數組的設定
memset(&press[0][0], 0, (n + 1) * (m + 1));
int i, j;
for(i = 1; i <= n; ++i)
{
for(j = 1; j <= m; ++j)
{
scanf("%d", &puzzle[i][j]);
if(puzzle[i][j] == 1)
puzzle[i][j] = 0;
else if(puzzle[i][j] == 0)
puzzle[i][j] = 1;
}
}
//枚舉所有可能的開燈方式,傳回可能的最小步數
int sum = enumate();
if (sum == -1)
printf("no solution\n");
else
printf("%d\n", sum);
}
return 0;
}