這一題大意如下:
一個冰箱上有4*4共16個開關,改變任意一個開關的狀態(即開變成關,關變成開)時,此開關的同一行、同一列所有的開關都會自動改變狀態。要想打開冰箱,要所有開關全部打開才行。
輸入:一個4×4的矩陣,+表示關閉,-表示打開;
輸出:使冰箱打開所需要執行的最少操作次數,以及所操作的開關坐標。
這一題很多人使用BFS的方法,其實還有一種更為精妙的方法,值得大家思考。
這種方法網上很多人都提到了,但是說的都不太清晰,我想了很久也沒想明白,后來我用程序追蹤一下運行的過程,才豁然開朗。
首先要明白最基本的原理:對一個開關進行操作n次,如果n為偶數,那么這個開關以及同行、同列的開關狀態都不發生改變,等價於沒有操作;如果n為奇數,那么這個開關以及同行同列的開關狀態全都發生改變,等價於只操作了一次。
要想使所有開關狀態全部打開(全部是-),就要把所有+變成-,所有-不改變。我們要做的就是找到一種“公式”,策略,使得不改變已經打開的開關狀態的情況下,把關閉的開關打開。這點很類似於魔方(PS:玩過魔方的都知道,魔方所謂的公式,其實就是在不改變已經拼好的部分的情況下,把其他部分一點一點添加到已拼好的部分)。
我們找到的策略就是:把開關本身以及其同一行同一列的開關(總共7個)都進行一次操作,結果是,開關本身狀態改變了7次,開關同一行、同一列的開關狀態改變了4次,其他開關狀態改變了2次。如下圖所示。
假如開關坐標為第二行第三列的(2,3),那么按照上述策略(把開關本身以及其同一行同一列的開關都進行一次操作),結果分析如下:
對於黃色部分的開關,只有與此黃色開關同一行和同一列的兩個紅色開關操作時,此黃色開關的狀態才會發生改變,因此所有黃色部分狀態改變次數為2,相當於0次
對於紅色部分的開關,只有與此紅色開關同一列或同一列的開關操作時,此紅色開關狀態才會發生改變,一行或者一列有4個開關,因此紅色部分開關狀態改變次數為4,相當於0次
對於最原始的那個黑色開關,所有紅色開關操作時,它的狀態改變一次,然后黑色開關自己操作一次,因此黑色開關狀態改變7次,相當於改變1次。
總結上述分析可以得出結論,把開關本身以及其同一行同一列的開關都進行一次操作,最終結果是只有開關本身狀態發生變化,其他所有開關狀態都不變。
策略找到之后,那我們就想,如果對於所有關閉着的開關都進行一次上述策略,那么肯定是能把冰箱打開的,下面我們要做的就是把一些無用的,重復的操作去掉即可。
用一個4*4的數組記錄每個開關操作的次數,初始化為0,開關操作一次,記錄就+1,以樣例(http://poj.org/problem?id=2965)為例:
-+--
----
----
-+--
1 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
1 1 0 0
0 0 0 0
0 0 0 0
0 0 0 0
1 1 1 0
0 0 0 0
0 0 0 0
0 0 0 0
1 1 1 1
0 0 0 0
0 0 0 0
0 0 0 0
1 1 1 1
0 1 0 0
0 0 0 0
0 0 0 0
1 1 1 1
0 1 0 0
0 1 0 0
0 0 0 0
1 1 1 1
0 1 0 0
0 1 0 0
0 1 0 0
1 1 1 1
0 1 0 0
0 1 0 0
1 1 0 0
1 1 1 1
0 1 0 0
0 1 0 0
1 2 0 0
1 1 1 1
0 1 0 0
0 1 0 0
1 2 1 0
1 1 1 1
0 1 0 0
0 1 0 0
1 2 1 1
1 2 1 1
0 1 0 0
0 1 0 0
1 2 1 1
1 2 1 1
0 2 0 0
0 1 0 0
1 2 1 1
1 2 1 1
0 2 0 0
0 2 0 0
1 2 1 1
對於樣例中的每一個+開關,進行一次策略,記錄數組所記錄的每一個開關操作的次數變化如上所示。那么在最終得到的數組中可以看出,有些開關操作了偶數次,有些操作了奇數次。操作了偶數次的開關就是上面所說的無用的,重復的操作,直接去掉,留下奇數次的就最終的答案。
代碼寫的不好,僅供參考
#include<stdio.h> #include<malloc.h> char handle[4][4]; //存儲字符 int record[4][4]; //記錄每一位操作次數 void set_handle() { int i, j; for(i = 0; i < 4; i++) { scanf("%s", handle[i]); } memset(record, 0, sizeof(record)); } char change_state(char state) { if(state == '+') return '-'; return '+'; } //把m行n列的開關按下 void flip(int m, int n) { int i, j; for(j = 0; j < 4; j++) //行操作 { handle[m][j] = change_state(handle[m][j]); } for(i = 0; i < 4; i++) //列操作 { if(i != m) handle[i][n] = change_state(handle[i][n]); } record[m][n]++; //執行一次操作 } //對數組中所有+位置的行和列所有的開關執行一次flip操作 void full_flip() { int i, j; for(i = 0; i < 4; i++) { for(j = 0; j < 4; j++) { if(handle[i][j] == '+') { int t; for(t = 0; t < 4; t++) { flip(i, t); } for(t = 0; t < 4; t++) { if(t != i) { flip(t,j); } } } } } } //遍歷record數組,統計操作次數 void solute() { int count = 0; int i, j; for(i = 0; i < 4; i++) { for(j = 0; j < 4; j++) { if(record[i][j] & 1 != 0) { count ++; } } } printf("%d\n", count); for(i = 0; i < 4; i++) { for(j = 0; j < 4; j++) { if(record[i][j] & 1 != 0) { printf("%d %d\n", i+1, j+1); } } } } int main(void) { set_handle(); full_flip(); solute(); return 0; }