(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)位置后面的位置,因此,在递推求解时,首先知道的是(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; }