poj2965 較為巧妙的演算法

2022-08-11 11:36:14 字數 2878 閱讀 4645

這一題大意如下:

乙個冰箱上有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 ...