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

 

时间: 2024-10-11 03:29:05

ACM解题总结——HihoCoder1048的相关文章

ACM解题之在线翻译 Give Me the Number

Give Me the Number Time Limit: 2 Seconds                                     Memory Limit: 65536 KB Numbers in English are written down in the following way (only numbers less than 109 are considered). Number  abc,def,ghi is written as "[abc] million

【宿舍菜鸟们的ACM解题笔记】487-3279

题目来源 北大ACM,题目ID 1002,难度 初级. 题目简介 Description Businesses like to have memorable telephone numbers. One way to make a telephone number memorable is to have it spell a memorable word or phrase. For example, you can call the University of Waterloo by dia

ACM解题感悟

对若干个字符串进行字典排序,应用结构体数组储存字符串(二维数组会报错),以用函数 sort 进行排序.题目:HDU1113 struct w { char res[15]; }wd_rs[100]; bool cmp(struct w a,struct w b) { return strcmp(a.res,b.res)<0;//升序 } sort(wd_rs,wd_rs+z,cmp);

ACM解题&amp;数据结构!

暂时只写一点平时能用到的 优先队列 本质是堆,堆的本质嘛,数组or指针~还是那句话!数组模拟世间万物! std::priority_queue<int>Q; Q.top();//返回队列里最大的值 Q.pop(): 树状数组 只能完成前缀和的查询~ 1.lowbit(i):获取的是最后一位1在哪 2.单点修改,void change(int x,int d) 区间查询,ll query(int x) 3.现在有1~9,那么查询3~9:(1~9)-(1~2): 按照二进制一位一位地查,从小向大开

wechall.net/stegano 解题心得

最近迷上了 www.wechall.net 网站,里面都是些与计算机相关的题目挑战.题目又分很多类型,例如:加密与解密.隐写术.网络攻防.趣味编程.数学逻辑等.题目有的简单,有的很难,需要一些知识和技巧.与其他题目挑战的网站不同的是,在其他类似性质的网站注册的用户可以绑定到 WeChall 网站,然后 WeChall 提供排名信息,而且也分得很细,什么按总分全球排名.什么在自己国家的排名.什么解答某种语言网站题目的排名等.可以从解题的人数判断题目的难易程度,有兴趣的朋友可以去注册,解题中也能学到

局部变量和子函数的应用

西南科技大学 ACM解题报告 姓名:张艺童 学号:5120142109 组号:3 班级:软件1402 1 题目来源:OJ 0615 2  题目描述: Description 输出[m,n]间的所有素数,并且每5个换行,如果区间内不存在素数,输出0 Input Output Sample Input 1 3 17 Sample Ouput 1 2 3 3 5 7 11 13 17 3 题目分析及知识点: 知识点是判断一个数是否为素数: 重点在于怎样在判断了某个数是否为素数之后能够将其输出,建立动态

成绩排序查找

西南科技大学 ACM解题报告 姓名:张艺童 学号:5120142109 组号:3 班级:软件1402 1 题目来源:OJ  0616 2 题目描述: Description: 用选择法对N个学生的成绩按从大到小的顺序排序,N个学生的成绩整数用scanf 输入,输入的成绩在[0,100]之间.排序完成后,输入一个成绩,要求用逐个比较查找的方式找出该成绩是该组中第几个元素的值(即第几名).如果该成绩不在数组中,则输出“no this score!”. 要求: 1.把排序算法写成函数形式,在主函数中输

待刷题目分类

各大OJ题目归类 Posted on 2012 年 8 月 8 日 by admin ---------–最优化问题------------- --------动态规划 SOJ1162 I-Keyboard SOJ2096 Maximum Submatrix SOJ2111 littleken bg SOJ2505 The County Fair SOJ2818 QQ音速 SOJ2469 Exploring Pyramids SOJ1833 Base Numbers SOJ2009 Zeros

08年acm区域赛北京赛区 部分题解题报告

08年区域赛北京赛区 http://poj.org/searchproblem?field=source&key=Beijing+2008 POJ 3921 Destroying the bus stations 题目还是比较难的,当时的榜似乎只有4/25的通过/提交,其实题目数据很水.学长转换模型写了网络流求最小割,可以AC,不过自己造了个数据推翻了正确性.我写了个很挫的bfs套bfs,外层是最小的删除点数,内层是求最短路,数据很水可以AC.但比较蛋疼的在于bfs耗内存,而且队列中的点数是阶乘