ACM解题总结——HihoCoder1048

(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;
}

        
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值