OpenJudege-2811 熄燈問題 解題報告

OpenJudge - 2811

問題描述

熄燈問題

有一個由按鈕組成的矩陣,其中每行有6個按鈕,共5行。每個按鈕的位置上有一盞燈。當按下一個按鈕後,該按鈕以及周圍位置(上邊、下邊、左邊、右邊)的燈都會改變一次。即,如果燈原來是點亮的,就會被熄滅;如果燈原來是熄滅的,則會被點亮。在矩陣角上的按鈕改變3盞燈的狀態;在矩陣邊上的按鈕改變4盞燈的狀態;其他的按鈕改變5盞燈的狀態。這裏寫圖片描述
在上圖中,左邊矩陣中用X標記的按鈕表示被按下,右邊的矩陣表示燈狀態的改變。對矩陣中的每盞燈設置一個初始狀態。請你按按鈕,直至每一盞等都熄滅。與一盞燈毗鄰的多個按鈕被按下時,一個操作會抵消另一次操作的結果。在下圖中,第2行第3、5列的按鈕都被按下,因此第2行、第4列的燈的狀態就不改變。這裏寫圖片描述
請你寫一個程序,確定需要按下哪些按鈕,恰好使得所有的燈都熄滅。根據上面的規則,我們知道1)第2次按下同一個按鈕時,將抵消第1次按下時所產生的結果。因此,每個按鈕最多只需要按下一次;2)各個按鈕被按下的順序對最終的結果沒有影響;3)對第1行中每盞點亮的燈,按下第2行對應的按鈕,就可以熄滅第1行的全部燈。如此重複下去,可以熄滅第1、2、3、4行的全部燈。同樣,按下第1、2、3、4、5列的按鈕,可以熄滅前5列的燈。

Input

5行組成,每一行包括6個數字(0或1)。相鄰兩個數字之間用單個空格隔開。0表示燈的初始狀態是熄滅的,1表示燈的初始狀態是點亮的。

Output

5行組成,每一行包括6個數字(0或1)。相鄰兩個數字之間用單個空格隔開。其中的1表示需要把對應的按鈕按下,0則表示不需要按對應的按鈕。

Sample Input

0 1 1 0 1 0
1 0 0 1 1 1
0 0 1 0 0 1
1 0 0 1 0 1
0 1 1 1 0 0

Sample Output

1 0 1 0 0 1
1 1 0 1 0 1
0 0 1 0 1 1
1 0 0 1 0 0
0 1 0 0 0 0

題目大意

熄燈問題,顧名思義,要將給定的燈矩陣全部熄滅,其中,燈亮用1表示,燈滅用0表示,即最終要使燈矩陣變爲0矩陣。輸出中,1表示按下相應燈按鈕,0表示不按相應燈按鈕,最終要求輸出一個按按鈕的方案,能使得所有燈熄滅。
另外還有一點要注意,每個燈最多只需要按一次,不存在一盞燈按多次的狀況,因爲兩次按下一盞燈,相當於沒按。

解題方法

首先這是一道枚舉。


首先是枚舉方法

但是要枚舉,我們不能枚舉所有的燈的方案,這樣共有230 種,會超時,因此要設計一種較少的枚舉方法。
我們有以下思路:
枚舉第一行的所有狀態,在第二行,按下第一行沒有熄滅的燈對應的第二行按鈕,這樣,第一行燈即可完全熄滅。如:
這裏寫圖片描述
(按下第二行的幾盞燈,使得第一行的所有燈熄滅)
第二行、第三行、第四行、第五行以此類推,這樣我們一定能夠保證前四行的燈全部熄滅,再判斷第五行燈的狀況,若全部熄滅,則說明這種方法可行,因爲題目只要求輸出一種方案,則輸入這種方案,程序結束。若第五行的燈沒有全部熄滅,說明第一行的枚舉方式不正確,需要重新枚舉。
這樣,我們就將枚舉整個矩形的工程簡化爲枚舉第一行的工程,此時最多需要枚舉26 ,即64種方案。


接下來是燈和狀態的表示方法。
要枚舉和表示這些01組合,用一個十進制數組,再進行6重循環的方法可行,但浪費空間,而且不夠簡潔。因此我們可以設想用二進制數字枚舉,而一個char類型的字符佔一個字節,8個比特,這已經足夠我們枚舉所有的方案,因此,只需要使用char類型中ASCII碼爲0的字符到ASCII碼爲63的字符,便可以枚舉到所有組合。(二進制從右往左數以此爲第1位、第2位…,在枚舉中,我們用第 i(1 <= i <= 6) 位模擬第 i 盞燈的狀況。)
在按按鈕的方案中,我們同樣適用這種二進制表示法,在輸出時,只需要通過位運算,輸出每個每個二進制位即可。
下列函數爲幾個需要用到的二進制操作函數。


獲取二進制的某一位

int GetBit(char c, int i)
{
    return (c >> i) & 1;
}

設置二進制的某一位

void SetBit(char &c, int i, int v)
{
    if(v)
        c = c | (1 << i);
    else
        c = c & ~(1 << i);
}

翻轉二進制的某一位

void FilpBit(char &c, int i)
{
    c = c ^ (1 << i);
}

最後是確定某一行燈的狀況
我們可以通過二進制異或操作方便的獲取某一行燈在按下按鈕後的狀況
即 1^1 = 0,一個亮燈按一次變爲0(滅),1^0 = 1,一個燈不按還是1(亮),0^0 = 0,一個滅燈不按爲0(滅),0^1 = 1,一個滅燈按一次變爲1(亮)。
因此,通過一個異或運算就可以獲得當前行的燈的狀況。

代碼如下

#include <iostream>
#include <cstdio>
#include <cstring>
#include <memory>
using namespace std;
int T;
char oriLights[10];
char Lights[10];
char result[10];
int GetBit(char c, int i)
{
    return (c >> i) & 1;
}
void SetBit(char &c, int i, int v)
{
    if(v)
        c = c | (1 << i);
    else
        c = c & ~(1 << i);
}
void FilpBit(char &c, int i)
{
    c = c ^ (1 << i);
}
void OutputResult(int t)
{
    cout << "PUZZLE #" << t << endl;
    for(int i = 0; i < 5; i ++)
        for(int j = 0; j < 6; j ++)
        {
            cout << GetBit(result[i], j);
            if(j < 5)
                cout << " ";
            else
                cout << endl;
        }
}
int main()
{
    cin >> T;
    char switches;
    for(int t = 1; t <= T; t ++)
    {
        memset(oriLights, 0, sizeof(oriLights));
        int v;
        for(int i = 0; i < 5; i ++)
            for(int j = 0; j < 6; j ++)
            {
                cin >> v;
                SetBit(oriLights[i], j, v);
            }
        for(char c = 0; c < 64; c ++)
        {
            memcpy(Lights, oriLights, sizeof(oriLights));
            switches = c;
            for(int i = 0; i < 5; i ++)
            {
                result[i] = switches;
                for(int j = 0; j < 6; j ++)
                    if(GetBit(switches, j))
                    {
                        if(j > 0)
                            FilpBit(Lights[i], j - 1);
                        FilpBit(Lights[i], j);
                        if(j < 5)
                            FilpBit(Lights[i], j + 1);
                    }
                if(i < 4)
                    Lights[i + 1] ^= switches;
                switches = Lights[i];
            }
            if(Lights[4] == 0)
            {
                OutputResult(t);
                break;
            }
        }
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章