標籤:
(p.s:第一次做狀態壓縮dp的題目,真是把俺折騰到死。。。。)
題目來源:
HihoCoder 1048
題目要求:
小Hi和小Ho領到了一個大小為N*M的長方形盤子,他們可以用這個盒子來裝一些大小為2*1的蛋糕。但是根據要求,他們一定要將這個盤子裝的滿滿的,一點縫隙也不能留下來,才能夠將這些蛋糕帶走。
於是他們提出了一個問題——他們有多少種方案來裝滿這個N*M的盤子呢?
解答:
題目的要求是用一個1×2的蛋糕來完美覆蓋N×M大小的盤子,計算不同的覆蓋方案的數目。由於要求盤子的任何角落都被覆蓋,因此,我們在解答時可以對盤子的每個位置進行分析,枚舉每一個位置的蛋糕的擺放方式,進行求解。具體求解如下:
·枚舉:
採用二維座標系對於盤子的每一個位置進行定義,左上方為(1, 1), 右下角為(N, M),以從上到下、從左至右的方式用蛋糕填滿每一個位置。在對某一個位置(i, j)進行擺放時,可以分為如下幾種情況:
(1) 該位置已經被之前擺放的蛋糕覆蓋。此時,我們需要枚舉下一個位置: (2)該位置尚未被覆蓋。此時需要枚舉蛋糕的擺放位置,此時有“橫放”和“豎放”兩種情況: 對於某些情況,並不是橫放和豎放都可以: 還有一些情況,可能無解:
·計算: 基於以上的枚舉方法,可以發現:當枚舉到位置(i,j)時,前1 ~ (i-1)行一定是全部填滿的;第i行和第i+1行的情況則不確定;(i+2) ~ N 行則一定為空白,如:
利用這樣的特性,就可以定義每一種擺放方案。由於前1 ~ (i-1)行的情況和 最後的(i+2) ~ N行的情況是確定的,因此不需要記錄。需要記錄的是第i行和第i+1行的情況。
記:第i行的狀態為:p1, p2, ... pk ... pm,其中pk表示第i行第k個位置的覆蓋情況,0為未覆蓋,1為已覆蓋;
同理:記第i+1行的狀態為:q1, q2, ... qk ... qm,其中qk第i+1行第k個位置的覆蓋情況,0為未覆蓋,1為已覆蓋。
同時記錄當前枚舉的位置為(i, j)。
定義在目前狀態下繼續擺放的可行的方法數為sum(i, j; p1, p2, ... pm;q1, q2, ... qm)。此時,計算sum的值需要分情況考慮。嘗試在(i, j)位置以橫放或豎放的方式擺放蛋糕就會得到不同的擺放方案。而不同的擺放方式就會將問題引入新的子問題求解。 具體情況如下:
(1) 如果位置(i,j)已經被覆蓋,那麼當前sum值就等於下一枚舉位置對應的sum值:
下一個枚舉位置可能和當前位置是同一行:
sum(i, j; p1, p2, ... pm;q1, q2, ... qm)
= sum(i, j+1; p1, p2, ... pm;q1, q2, ... qm)
如果當前位置是本行的最後一個位置,那麼下一個枚舉位置是下一行的第1個位置:
sum(i, j; p1, p2, ... pm;q1, q2, ... qm)
= sum(i+1, 1; q1, q2, ... qm;0, 0, ... 0)
注意此時操作的行有變化,因此p序列和q序列也要對應變化。
(2) 如果位置(i,j)未被覆蓋,則根據橫放和豎放的方式進行考慮:
橫放:
sum(i, j; p1, p2, ...pj=1, pj+1=1, pm;q1, q2, ... qm)
豎放:
sum(i, j; p1, p2, ...pj=1, pm;q1, q2, ... qj=1, qm)
注意,並不是所有的位置都可以橫放和豎放,因此還需要對具體情況進行具體分析。
(3) 如果位置(i,j)未被覆蓋,但是橫放和豎放均不可以進行,這表明從當前的局面出發,不管以何種方式繼續擺放,位置(i,j),均無法被覆蓋,此時沒有合法的方案,sum值為0。
(4) 對於盤子中個最後一個位置(N,M),還需要進行特殊考慮。考慮sum(N, M; 1, 1, ... 1; q1, q2, ... qm)的值,它表示當前所有的位置均被覆蓋,這是一個我們需要的結果,此時的q序列沒有意義,因為下一行根本不存在。這裡為了便於理解可以假設存在第N+1行,由於我們已經擺放完成,因此如果繼續枚舉下去,擺放方式只有1種就是“不放”。因此:
sum(N, M; 1, 1, ... 1; q1, q2, ... qm) = 1
這個值作為整個遞推求解的初始化操作。
最後總結一下sum值的計算方式:
sum(i, j; p1, p2, ... pm;q1, q2, ... qm) =
① sum(i, j+1; p1, p2, ... pm;q1, q2, ... qm)
(i≤N, j<M, pj=1) [當前位置已覆蓋,下一位置在同一行]
② sum(i+1, 1; q1, q2, ... qm; 0, 0, ... 0)
(i<N, j≤M,pj=1) [當前位置已覆蓋,下一位置在下一行]
③ sum(i,j; p1, p2 ...pj=1, pj+1=1, ... pm; q1, q2, ... qm)
(i<N∧qj=1∨i=N, j<M, pj=0, pj+1=0) [當前位置未覆蓋,僅可以橫放]
④ sum(i,j; p1, p2 ... pj=1 ... pm; q1, q2, ... qj=1 ... qm)
(i<N, j<M∧pj+1=1∨j=M, pj=0, qj=0) [當前位置未覆蓋,僅可以豎放]
⑤ sum(i,j; p1, p2 ...pj=1, pj+1=1, ... pm; q1, q2, ... qm)
+ sum(i,j; p1, p2 ... pj=1 ... pm; q1, q2, ... qj=1 ... qm)
(i<N, j<M, pj=0, pj+1=0, qj=0) [當前位置未覆蓋,可以橫放也可以豎放]
⑥ 0 (i<N∧qj=1∨i=N,j<M∧pj+1=1∨j=M, pj=0) [當前位置未覆蓋,不可以橫放也不可以豎放]
⑦ 1 (i=N, j=M, p=1, 1, ... 1) [(N,M)位置的特殊情況,初始狀態]
可以發現,(i,j)位置的sum值的求解依賴於(i,j)位置後面的位置,因此,在遞推求解時,首Crowdsourced Security Testing道的是(N,M)位置的sum值,然後從下到上,從右至左,和枚舉順序相反的順序依次就可以依次求解每個位置的sum值。最後sum(1, 1, 0, 0, ... 0; 0, 0, ... 0)就是我們需要的最後的答案。
·狀態壓縮:
上文中提供了一種計算方式。但問題在於sum值包含太多的參數,編程很不方便。考慮到題目中M的值只有:3, 4, 5 三種情況,且數值很小,因此可以對於p序列和q序列進行狀態壓縮,具體思想就是,由於p序列和q序列的每個元素只可能取值1或0,因此可以將其看作一個位元,這樣,用最多10位二進位位(M=5)就可以滿足程式的要求。因此,sum值的表示方法就可以改為:sum(i,j,k),其中:i,j表示當前的枚舉位置,k則表示壓縮後的第i行和第i+1行的覆蓋情況,高位部分表示第i行,低位部分是第i+1行。對於不同的M值,k值的範圍是:0~2^(2*M)-1
·備忘:
注意在枚舉過程中,可能有些狀態是不合法的,例如:
sum(i,j; 0, 0, ... 0; 0, 0, ... 0)
如果j>0,那麼該值包含的p序列表明在(i,j)位置的左邊還有位置沒有被覆蓋,根據我們前面枚舉的從上到下、從左至右的順序,這種情況是不可能存在的。但是,這並不會影響到最後的結果,因為,我們一定是在覆蓋了(i,j)位置的情況下,才會遞推到後面的狀態進行求解,因此,對於最終的結果 sum(1, 1, 0, 0, ... 0; 0, 0, ... 0),它一定不會依賴於這些非法狀態的值。
以上就是本題的求解過程,演算法的時間和空間複雜度都是O[N*M*2^(2*M)],效率不高,但本題宗旨不在於在資料量和執行效率上設定難度,因此這樣演算法可以通過。
輸入輸出格式:
輸入:每個測試點(輸入檔案)有且僅有一組測試資料。為兩個正整數N、M,表示盤子的大小。
輸出:輸出一行,不同的擺放方案的數目,考慮到總的方案數可能非常大,只需要輸出方案數除以1000000007的餘數
資料範圍:
2 ≤ N ≤ 1,000
3 ≤ M ≤ 5
程式碼:
/****************************************************//* File : HihoCoder1048 *//* Author : Zhang Yufei *//* Date : 2016-05-03 *//* Description : HihoCoder ACM program. (submit:g++)*//****************************************************/#include<stdio.h>#include<stdlib.h>#include<math.h>#define MOD 1000000007// Record the input.int N, M;// Define the matrix for dp.int ***dp;/* * This function gets the bit from the state according to the given position. * Parameters: *@state: The state value. *@position: The position to get. *@tag: If tag = 0, get bit from p1, p2 ... pm; if tag = 1, get bit from *q1, q2 ... qm. * Returns: *The result bit; */int get_bit(int state, int position, int tag);/* * This function puts a piece of cake in the given postion. * Parameters: *@state: The original state. *@y: The position to put (only y coordinate value). *@tag: If tag = 1, put the cake in horizontical way, or in vertical way. * Returns: *The new status value after putting. */int put_cake(int state, int y, int tag);int main (void) {scanf ("%d %d", &N, &M);int state_cnt = pow (2, M * 2);dp = (int***) malloc (sizeof (int**) * N);for(int i = 0; i < N; i++) {dp[i] = (int**) malloc (sizeof (int*) * M);for(int j = 0; j < M; j++) {dp[i][j] = (int*) malloc (sizeof (int) * state_cnt);}}for(int i = N - 1; i >= 0; i--) {for(int j = M - 1; j >= 0; j--) {for(int k = state_cnt - 1; k >= 0; k--) {int pj = get_bit(k, j, 0);if(pj == 1) {if(j < M - 1) {dp[i][j][k] = dp[i][j + 1][k];} else if(j == M - 1){if(i == N - 1) {dp[i][j][k] = 1;} else {dp[i][j][k] = dp[i + 1][0][(k << M) % state_cnt];}}} else {dp[i][j][k] = 0;if(j < M - 1) {int pj1 = get_bit(k, j + 1, 0);if(pj1 == 0) {int state = put_cake(k, j, 0);dp[i][j][k] += dp[i][j][state]; }}if(i < N - 1) {int qj = get_bit(k, j, 1);if(qj == 0) {int state = put_cake(k, j, 1);dp[i][j][k] += dp[i][j][state];dp[i][j][k] %= MOD;}}}}}}printf("%d\n", dp[0][0][0]);return 0;} /* * This function gets the bit from the state according to the given position. * Parameters: *@state: The state value. *@position: The position to get. *@tag: If tag = 0, get bit from p1, p2 ... pm; if tag = 1, get bit from *q1, q2 ... qm. * Returns: *The result bit; */int get_bit(int state, int position, int tag) {if(tag == 0) {state = state >> M;}int r = 0;for(int i = 0; i < M - position; i++) {r = state % 2;state /= 2; }return r;}/* * This function puts a piece of cake in the given postion. * Parameters: *@state: The original state. *@y: The position to put (only y coordinate value). *@tag: If tag = 1, put the cake in horizontical way, or in vertical way. * Returns: *The new status value after putting. */int put_cake(int state, int y, int tag) {int r = 1;for(int i = 0; i < M - y - 1; i++) {r = r << 1;}if(tag == 0) {r = r << M;state = state | r;r = r >> 1;state = state | r;} else {state = state | r;r = r << M;state = state | r; }return state;}
ACM解題總結——HihoCoder1048