204_蓄水池 动态规划学习例题C解决

题目

2020年c程序设计实践实训题中被考察。

任务描述

工地有一个n升蓄水池,现在需要将它灌满水(不能溢出),当第i次灌水的时候,可以灌入1至num[i-1]升水。

问有多少种灌满水的方法?答案可能很大,答案对1e9+7取模。

• 1 <= n <= 10^6
• 1 <= num[i] <=10^6

思路

题目的意思是,给我们数组的大小和内容,让我们计算达到最大数目的灌水方法有多少种。

例如 给出数字 4 1 3 2 1
意味着最终累加结果为4 可以灌水4次同时也是建立数组的依据
所以创建一个大小为4的数组装下剩下4个数字;
接下来,第一个数字为1,代表第一次只能灌1升水,结束;
第二个数字为3,代表第二次可以灌1、2、3升水,显然,如果第二次灌水3升,就达到了4(灌满了),这是一种情况。
第三次,可以灌水1、2升,以此类推,如果灌满了,就是一种情况。
比如121,112都是3次灌满的情况
四次灌满则是1111的情况
加起来一共4种

题目有点没讲清楚,看到这个题我理解了半天,在这里做以上梳理。
一开始我想用树转二叉树的原理强行遍历,建立左右儿子如此这般,然而只能坚持到二十五(运行十几秒钟),到50就躺平不动了。不知是何原理,空间上的问题我用了销毁节点的方式规避,时间上感觉也并不复杂,就是出不来。最终放弃了这种疑似炫技的行为。
接下来讲到动态规划的解法,动态规划就是一步一步算,第二次运用第一次的经验,一个个累加,得出最终的次数,这样只需要一步步建立一个二元数组就可以了。学到它的时候,我觉得就好像是小时候玩填图游戏一样的做法。不过仍然需要理解就是了。

数据结构

例如,4 4 3 2 1(其实这样设计的意思就是没有限制了,细品)
建立一个大小为4,5的二维数组
4是对应之前的数组,次数也从0开始计算
5是对应每次倒水达到的总水量(0空置不用方便理解)
形成如下数组,由于第0次可以灌水4,在1,2,3,4处标1表示一种倒水的方法
第一步
接下来看第1步,第一步得到水量为2的只有11一种,写上1;
得到水量为3的有12和21两种,写上2;
得到水量为4的有13,22,31三种,写上3;
第二步
第2步,得数为3只有21一种(在第1步中最小是2)
得数为4有22,或者31,而第1步中2有1种,3有2种,得到一共3种。
不难推出,第3步只有31,一种。
最后一步
将得数为4的所有可能相加,得到共有1+3+3+1=8种

下面是4 2 2 1 1的情况,这个可以看出限制
4 2 2 1 1

遍历

思考:我们在画上面的棋盘的时候是怎么做的呢,用4 2 2 1 1的图做例子
演变过程
表中【1,3】的2可以由第0步中的1加2得到,也可以用2加1得到,数值相加为2。
表中【2,4】的2是由第1步中的3加1得到,其中3的数值为2,而由于第2步只能加1升水,【1,2】这个格子中的数字1不用加(2升水加不到4升)。

可以发现,4 2 2 1 1中的后4个数字作用是限制格子中数字的累加
因此,作3层遍历:
第一层:代表加水的次数;
第二层:代表加水的量(限制格子中数字的累加);
第三层:代表上一层的每一个格子。

就这样完成条件判断,这个格子的数字由上一层哪几个格子相加得到。
每次第二层遍历过后,用一个计数器加上这一次到达总量的可能的次数(最后得到的就是最后一列数字的和)。

全部代码

#include<stdio.h> 
#include<string.h> 
#define mod 1e9+7
/*
当第i次灌水的时候,可以灌入1至num[i]升水。(从0开始计数) 
*/

int main()
{
	int counter=0; 
	int SUM=0,i,j,k;
	scanf("%d",&SUM);	
	int num[SUM];
	for(j=0;j<SUM;j++)
	{
		scanf("%d",&num[j]);	
	}
	//动态规划,第几次,有几升水,先用数组表示
	int attempt[SUM][SUM +1];
	//置零
	memset(attempt , 0 , sizeof(attempt)); 
	for(i=0;i<SUM;i++)//每次都循环到最后一次倒水
	{
	for(j=1;j<=num[i];j++)//循环到单次最大 
	 {
	 	if(i == 0) attempt[i][j] = 1;
	 	else
		 {//对于上一次的结果累加 
		 	for(k=i;k<=SUM-j;k++)
		 	{
		 		if(attempt[i-1][k]==0) continue;
		 		attempt[i][k+j] += attempt[i-1][k];
		 		if(attempt[i][k+j]>mod) attempt[i][k+j] -= mod;
			}
		 }
	
	  }	
	counter+=attempt[i][SUM];
	if(counter>mod) counter -= mod;
	}
	 
	  
 	printf("%d",counter);
	
	return 0;
}

温馨提示

  • 用memset函数把数据结构中的每一个数字初始化为0,方便今后的判断。
  • 第0次倒水要手动设置1。
  • 求余数最好用减法,因为这个判定要用很多次。
  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值