【ACM算法】-- 动态规划篇 - 递推求解

第一题:
在这里插入图片描述
在这里插入图片描述
思路:
对于递推问题的思路,现做如下解释:递推就是所求解问题可以类似于深搜化成很多状态,其中,每一个状态都是由其之前的状态所决定的,即可以由第一项,或前几项,递推求解以后的所有状态。

至于递推问题,我们一般有两种方法:

  1. 递归实现,由后向前,而且实现的方式是谁被决定,就是谁调用,举例:

     假如:A[t-2]+A[t-1]=A[t];
     这里是A[t]被A[t-2]和A[t-1]决定,所以在求A[t]的时候就要由A[t]状态调用A[t-1]、A[t-2]
    

    这里还有一个简单的技巧,就是递归一般都是由后向前,直到最开始的时候的初值,然后开始回推,依次求解所有状态。所以递归的时候传入的参数都是最后的求解状态n,由n向之前状态递推。

  2. 循环实现,由前向后,由初状态依次更新以后的状态:

     假如:A[t-2]+A[t-1]=A[t];
     这里是A[t-2]和A[t-1]决定A[t],所以可以由A[t-1]、A[t-2]根据循环依次求出以后的数值。
    

    这里的技巧是,因为这个不是递归,所以,我们可以由初状态根据循环推出末状态,而不是由末状态调用初状态。

注意:我们要明确一个观点,递推题求递推式的思路是(由后向前)推递推式,但是递推式的实现,我们可以用 递归(由后向前)或者循环(由前向后) 实现。还有一点也很重要,有的时候我们可以像处理日期问题那样子,因为已经有了递推式,所以我们可以预处理。提前算出答案。使时间复杂度降到O(1);

对于上题:

思路是,考虑末状态,末状态可以走一阶或者两阶,当走一阶的时候,对应有走n-1阶时的种数。同理,当走两阶的时候,对应有走n-2阶时的种数。其和即为末状态的时候的种数。

所以递推式是:F[n]=F[n-1]+F[n-2]

实现思路:

  1. 递归思路:从n开始依次调用的,注意这里还有一个剪枝或者优化过程,因为这个类似于斐波那锲数列,所以当计算F[n-1]的时候还会计算F[n-2]一次,这是无用的计算,所以我们可以用一个数组,在递归F[n-1]的时候就把每个中间结果存储下来,供后面F[n-2]调用使用。当使用预处理的时候,辅助数组是必须的。如果不用预处理,辅助数组不必须。但有更好。
  2. 循环思路:这里的循环思路是设置一个循环依次迭代,这里不需要一个辅助数组来防止无用计算,因为它就存在一个数组,用来循环,但是这里也存在一个优化问题,就是F[n]只取决于其前两个状态,所以这个数组长度我们不必要设置为N,只需要长度为2即可,这就有一点滚动数组的味道了,当然也可以设置为3,都可以,这里只介绍长度为2的情况,当长度为2时,数组中只存在F[1]、F[2],然后我们求F[3],将F[3]存在F[1]的位置,因为求F[4]的时候只需要F[3]、F[2],与F[1]无关了,之后依次滚动坐标,可以由对2取余实现下标滚动。但是一般机试给的空间创建数组足够了,不必要这么大费周折来优化空间。

代码如下:

#include<stdio.h>
long long F[91];
int main(){
	F[1]=1;
	F[2]=2;
	for(int i=3;i<=90;i++){
		F[i]=F[i-1]+F[i-2];
	}//预处理
	int n;
	while(scanf("%d",&n)!=EOF){
		printf("%lld\n",F[n]);
	}
	return 0;
}

第二题:错排公式
在这里插入图片描述
在这里插入图片描述
思路: 对于这道题,官方的解答是这样子的:
在这里插入图片描述
在这里插入图片描述
简要理解就是:

首先因为,n个信封全部装错,所以第n个信封是不能装第n封信的,分析如下:
(注意,这里讨论的焦点是末状态,也就是第n号信封的情况)

因为是n个信封,所以定义F[n]来记录含有n个信封时装错的状态,当考虑第n个信封的时候,有两种情况,如图:
在这里插入图片描述
上面一行代表信封,下面一行代表信

有两种情况,但都是针对于n来说的

  1. 当k=m时,只需要将m与n互换内容即可,而n是固定的,m可以有n-1种取值可能,所以这种情况有,(n-1)* F[n-2] 种情况。
  2. 当k!=m时,只需要将m与n互换内容,这时m里面装的是k号信。而n是固定的,所以m有n-1种情况,所以这种情况有(n-1)* F[n-1]种情况。

上面这个理解起来可能不是很好理解,我也只是将里面的大概意思描述一下,具体的,感觉他说的不是很通透,比如,第二种情况,m为什么是n-1种情况,因为m可以取1~n-1中不为k的任意值,为啥不是n-2,我是想不出来了。

下面是一种更加简单,且通俗易懂的说法,方法分两步:

  1. 考虑第n号信,将第n号信放到其余任意信封中,有n-1种,这是肯定的,也是必须发生的。
  2. 假如放到了第k号信封中,现在考虑第k号信放到哪里,有两种情况:
    1. 放到第n号信封中,交换后就会有F[n-2]种结果。
    2. 放到其他除第n号信封的任意一个信封中,这里我们不关心,放到其他信封有多少种情况,因为都是同一结果,我们只关心,第k号信是否在第n号信封中,这里是不在,交换后就剩下F[n-1]种情况。

这时两个步骤合起来计算,计算结果是和上面的一样,但是更加的好理解了。

综上: F[n]=(n-1)*F[n-2]+(n-1)*F[n-1]

代码如下:

#include<stdio.h>
long long F[21];
int main(){
	F[1]=0;
	F[2]=1;
	for(int i=3;i<=20;i++){
		F[i]=(i-1)*F[i-1]+(i-1)*F[i-2];
	}
	int n;
	while(scanf("%d",&n)!=EOF){
		printf("%lld\n",F[n]);
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值