這一題大意如下:
乙個冰箱上有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,以樣例(為例:
-+--
----
----
-+--
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#includechar handle[4][4]; //儲存字元int record[4][4]; //記錄每一位操作次數
void set_handle()
memset(record, 0, sizeof(record));
}char change_state(char state)
//把m行n列的開關按下
void flip(int m, int n)
for(i = 0; i < 4; i++) //列操作
record[m][n]++; //執行一次操作
}//對陣列中所有+位置的行和列所有的開關執行一次flip操作
void full_flip()
for(t = 0; t < 4; t++)}}
}}}//遍歷record陣列,統計操作次數
void solute()}}
printf("%d\n", count);
for(i = 0; i < 4; i++)}}
}int main(void)
再談poj2965(高效演算法)
在列舉分類中已有暴力列舉的方法解這道題。之後在網上看到大神的高效演算法,膜拜之。故copy在此。證明 要使乙個為 的符號變為 必須其相應的行和列的運算元為奇數 可以證明,如果 位置對應的行和列上每乙個位置都進行一次操作,則整個圖只有這一 位置的符號改變,其餘都不會改變.設定乙個4 4的整型陣列,初值...
poj 2965 解題報告
就是對乙個4x4的棋盤進行翻轉,每一次翻轉都將讓同一行和列一起翻轉,直到所有符號都變為 時成功。通過列舉加上深度優先搜尋的方法進行解決,列舉通過行號和列號順序進行,每個位置都有翻轉和不翻轉兩種選擇 通過乙個位置兩次翻轉來回溯 poj 2965 244k 844ms include using nam...
poj 2965 遞迴 列舉
本題與1753思路一樣,區別就在於要記錄位置。deep是當前進行到了哪一步,step是判斷用step步是否可以完成,因此記錄位置只需在change 後做,回溯的時候雖然會說明上一步無效,但不用修改記錄,因為下一次記錄會覆蓋它。include using namespace std bool map ...