標籤:
題意:給你一個n*n的01矩陣,讓你把這個矩陣中盡量少的0轉換成1,使得矩陣每個位置的上下左右四個相鄰的數加起來能被2整除,求最少的轉換數
首先,n 的規模並不大,最大隻有15。但是完全枚舉整個矩陣顯然是不可能的(2225 ≈ 5 * 1067)。但是我們可以枚舉第一行,然後用第一行來算出後面的所有行。
但是,怎麼算呢?
先來說下演算法。對於每一行,我們通過他上面的兩行來決定他的值。如果上面兩行得到值為奇數,那麼這一行就賦值為 1 ,否則賦值為 0 。 然後與原始矩陣比較,如果是由 1 變 0 那麼這種情況是不允許的,於是繼續枚舉第一行,重新計算後面的。如果是由 0 變 1(或不變) 那麼儲存下來。最後在計算所有的格子之後,遍曆一下就能統計轉化數。然後取最小值就可以了。
下面用題目中給的第二組資料來示範一下如何通過上一行來計算下一行。
問題 正解
0 0 0 ==> 0 1 0
1 0 0 ==> 1 0 1
0 0 0 ==> 0 1 0
來舉例說明。首先我們通過枚舉第一行可以達到以下情況
如下表我們開始通過第一行來計算第二行,對於 x 所在的這一格來說,把這一格當做他上面一格的下面相鄰的格子進行計算,於是對於第一行第一列來說,他上下左右相加起來為 1 + x,因為要保證是偶數,所以 x = 1。接著與原矩陣進行比較,是符合題目要求的轉化(不變)。保留,繼續計算下一個。
如下表開始計算第二行第二列,按照上面的方法,計算得出 0 + 0 + x 要為偶數,所以 x = 0。與原數組比較依舊是符合題目要求的。
如下表計算第二行第三列,同上,得出 1 + x 要為偶數,所以 x = 1。與原矩陣比較,發現是符合要求的轉化(0 變 1)繼續。
這樣第二行就得出來了,是下面這種情況:
按照上述方法,可以計算出第三行,來完成這個例子的答案:
最後在說一下枚舉第一行的 2 種方法,第一個種自然是直接深度優先搜尋(DP)直接進行枚舉。第二種可以利用位元運算(末尾附上的AC代碼採用的方法)進行枚舉。因為整數在記憶體中是用二進位儲存的。只要讓 s 從 0 開始一直加到 2n-1 – 1 這麼大就可以枚舉所有的情況。
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 using namespace std; 5 const int N = 20 + 5; 6 const int INF = (1 << 30) - 1; 7 int a[N][N], dp[N][N],n; 8 9 int deal(int s){10 memset(dp, 0, sizeof(dp));11 for(int c = 0; c < n; c++){12 if(s & (1 << c)) dp[0][c] = 1;13 else if(a[0][c] == 1) return INF;14 }15 for(int r = 1; r < n; r ++){16 for(int c = 0; c < n; c ++){17 int sum = 0;18 if(r > 1) sum += dp[r-2][c];19 if(c > 0) sum += dp[r-1][c-1];20 if(c < n) sum += dp[r-1][c+1];21 dp[r][c] = sum % 2;22 if(dp[r][c] == 0 && a[r][c] == 1)return INF;23 }24 }25 int ret = 0;26 for(int r = 0; r < n; r ++)27 for(int c = 0; c < n; c ++)28 if(a[r][c] != dp[r][c]) ret++;29 return ret;30 }31 32 33 int main(){34 int T;35 scanf("%d", &T);36 for(int cas = 1; cas <= T; cas ++){37 scanf("%d", &n);38 for(int r = 0; r < n; r ++)39 for(int c = 0; c < n; c ++) 40 scanf("%d", &a[r][c]);41 int ans = INF;42 for(int s = 0; s < (1 << n); s ++)43 ans = min(ans, deal(s));44 printf("Case %d: %d\n", cas, ans == INF ? -1 : ans);45 }46 return 0;47 }
【轉載】UVa 11464 Even Parity 偶數矩陣