由于最近时间较少,所以拖了好长时间,向大家表示歉意
(>人<;)对不起
目录
————————————————正片开始————————————————
【第三节:递推算法】(开始有自主练习了)
0.预告
大家好,今天给大家带来的是“递推算法”。
还是一样,直接开始。
————————————————正片开始————————————————
1.介绍
递推法是一种重要的数学方法,在数学的各个领域中都有广泛的应用,也是计算机用于数值计算的一个重要算法。这种算法的特点是:一个问题的求解需要一系列的计算,在已知条件和所求问题之间总存在着相互的某些联系,在计算时,如果可以找到前后过程之间的数量关系(递推式),那么从问题出发逐步推到已知条件,这种方法就叫递推。无论顺推还是逆推,其中关键肯定是找到递推式。这种处理问题的方法能使复杂运算化为若干步重复的简单运算,充分发挥计算机擅长重复处理的特点。
递推算法的首要问题是得到相邻的数据项间的关系(递推关系)。递推算法避开了求通项的麻烦,把一个复杂问题的求解分解成了若干步简单运算。一般说来,可以将递推算法看作一种特殊的迭代算法。
2.例题
例3.1:数塔问题
[问题描述]
输入一个数字三角形(如左图),请编一个程序计算从顶到底的某处的一条路径,是该路径所经过的数字总和最大。注:只要求输出总和。
1.一步可沿左斜线向下或右斜线向下走;
2.三角形行数小于等于100;
3.三角形中的数字为0 ~ 99。
测试数据通过键盘逐行输入,如左图数据应以右图所示格式输入:
[算法分析]
此题解法有很多种,从递推思想出发,设想,当从顶层沿某条路径走到第i层向第i + 1层前进时,我们的选择一定是沿其下两条可行路径中最大数字的方向前进,因此,我们可以采用倒推的方法,设a[i][j]存放从i,j出发到达n层的最大值,则a[i][j] = max{ a[i][j] + a[i+1][j] , a[i][j] + a[i+1][j+1] },a[1][1]即为所求的数字总和最大值。
[参考程序]
#include <bits/stdc++.h>
using namespace std;
int n,a[101][101];
int main(){
cin >> n;
for(int i = 1;i <= n;i++)
for(int j = 1;j <= i;j++)
cin >> a[i][j]; //输入数字三角形的值
for(int i = n - 1;i >= 1;i--){
for(int j = 1;j <= i;j++)
{
if(a[i+1][j] >= a[i+1][j+1]) a[i][j] += a[i+1][j]; //路径选择
else a[i][j] += a[i+1][j+1];
}
}
cout << a[1][1] << endl;
return 0;
}
例3.2:
[问题描述]
满足的数列被称为斐波那契数列(Fibonnacci),它的前若干项是1,1,2,3,5,8,13,21,34
求此数列第n项(n ≥ 3)。
即
[参考程序]
#include <bits/stdc++.h>
using namespace std;
int main(){
int f0 = 1,f1 = 1,f2;
int n;
cin >> n;
for(int i = 3;i <= n;++i){
f2 = f0 + f1;
f0 = f1;
f1 = f2;
}
cout << f2;
return 0;
}
当然,还可以用数组:
#include <bits/stdc++.h>
using namespace std;
int n,a[10005];
int main(){
cin >> n;
a[1] = 1;
a[2] = 1;
for(int i = 3;i <= n;i++){
a[i] = a[i-1] + a[i-2];
}
cout << a[n];
}
有关斐波那契数列,我们先来考虑一个简单的问题:楼梯有n个台阶,上楼可以一步上一阶,也可以一步上两阶。一共有多少种上楼的方法?
这是一道计数游戏。在没有思路时,不妨试着找找规律。n=5时,一共有8种方法:
5 = 1 + 1 + 1 + 1 + 1
5 = 2 + 1 + 1 + 1
5 = 1 + 2 + 1 + 1
5 = 1 + 1 + 2 + 1
5 = 1 + 1 + 1 + 2
5 = 2 + 2 + 1
5 = 2 + 1 + 2
5 = 1 + 2 + 2
其中有5种方法第1步走了1阶(下划线方式),3种方法走了2阶,没有其他可能。假设f(n)为n个台阶的走法总数,把n个台阶的做法分成两类。
(1)第1步走1阶,剩下还有n-1阶要走,有f(n-1)种方法。
(2)第1步走2阶,剩下还有n-2阶要走,有f(n-2)种方法。
这样,就得到了递推式:f(n) = f(n-1) + f(n-2),不要忘记边界情况:f(1) = 1,f(2) = 2。把f(n)的前几项列出:1,2,3,5,8......
这样子就可以推出。
例3.3:
[问题描述]
有2*n的一个长方形方格,用一个1*2的骨牌铺满方格。
编写一个程序,试对给出的任意一个n(0 < n < 30),输出铺法总数。
[算法分析]
(1)面对上述问题,如果思考方法不恰当,要想获得问题的解答是相当困难的。我们可以用递推来归纳问题解的一般规律。
(2)当n = 1时,只能用一种铺法,即。
(3)当n = 2时,骨牌可以并列竖或横排,即。
(4)当n = 3时,骨牌可以全部竖排,也可以排1个竖排与2个横排(2种),即。
(5)推出一般规律:。
直接看代码:
#include <bits/stdc++.h>
using namespace std;
int main(){
int n,a[101];
cin >> n;
a[1] = 1;
a[2] = 2;
for(int i = 3;i <= n;i++){
a[i] = a[i-1] + a[i-2];
}
cout << a[n];
}
仅仅a[2]变了个数。
[运行输入输出]
输入:30
输出:1346269
(总体省略了斐波那契数列第一项,其他不变)
例3.4:
[问题描述]
在所有的n位数中,有多少个数中有偶数个数字3?请输出这个个数对12345取余的值。
[输入格式]
读入一个数n。
[输出格式]
输出有多少个数字中有偶数个数字3对12345取余的值。
[输入样例]
2
[输出样例]
73
[数据范围]
[样例说明]
在所有的2位数字中,包含0(注意0也是偶数)个3的数有72个,包含2个3的数有1个,共73个。
[算法分析]
方法1:排列组合(还需要用动态规划,以后再说)
方法2:递推
考虑这种题目,一般来说都是从第 i - 1 位推导第 i 位,且当前位是取偶数还是取奇数。我们可以用f[i][0]表示前 i 位取偶数个3有几种情况,f[i][1]表示前 i 位取奇数个3有几种情况。
那么状态转移方程就是:
f[i][0] = f[i-1][0] * 9 + f[i-1][1]
f[i][1] = f[i-1][0] + f[i-1][1] * 9
(边界条件:f[1][1] = 1;f[1][0] = 9)
[参考程序]
#include <bits/stdc++.h>
using namespace std;
int main(){
int f[1005][2],n,x;
cin >> n;
f[1][1] = 1;
f[1][0] = 9;
for(int i = 2;i <= n;i++){
x = f[1][0];
if(i == n) x--;
f[i][0] = (f[i-1][0] * x + f[i-1][1]) % 12345;
f[i][1] = (f[i-1][1] * x + f[i-1][0]) % 12345;
}
cout << f[n][0];
}
还是比较简单,直接下一道:
注:下一道题开始有难度了,做好准备
例3.5:过河卒(Noip2002)(自主练习)
[问题描述]
棋盘上A点有一个过河卒,需要走到目标B点。同时棋盘上某一点有一个对方的马,该马所在点位与跳跃一步可达到的点称为对方马的控制点,卒不能经过马的控制点。
卒和马的运动方式如下:
棋盘位置以坐标(n,m)()显示 ,卒默认的位置在(0,0),中点位置B坐标为(n,m)马的位置M需要给出,且
且
。现在要求你计算卒从A点能够到达B点路径的条数。\
[算法分析]
跳马是一道老的不能再老的题了(看时间就知道),在学回溯或搜索等算法时,应该也会遇见此题,当然,不仅是跳马,还有更早的这道题的变形(likeNoip1997初中组第三题)。当然,这道题目搜索能是能用,但运行后发现,当m和n大于15时就超时了。
这道题目分析一下就知道,要到达棋盘上的每一个点,只能从左边来,或者从上边来,那么根据加法原理(指完成一件事有多种方法,每种方法又包含不同的可能性,那么完成这件事的总可能性就是这些不同方法的可能性之和)(自行理解),到达某一点的路径数目就等于到达其相邻的上面的点和左边的点的路径数目之和,简单理解就是下图:
(求A点到B点的路径数)
差不多就是这样。
因此我们可以使用逐列(或逐行)递推的方法来做,马的控制点只需设置为0即可。
这道题讲解一下思路:(代码可以自己试一试)
我们可以用dp[i][j]表示到达点(i,j)的路径数目,f[i][j]表示点(i,j)有无障碍,若=0则无障碍,若=1则有障碍。
那么递推公式就可以写成如下形式:
边界有4个(确实有点多哈):
1.
2.
3.
4.
注意,当n = 20,m = 20时,路径条数可能会超,所以本蒟蒻温馨提醒一下,看一下基础算法1(高精度运算)再来做哈~
3.典型递推关系
1.斐波那契数列
这个想必大家都知道,上面也讲了,直接过(其实是懒......)
2.汉诺塔问题
这里串一下,蒟蒻在今年csp中最后一道汉诺塔只对了3道QWQ,关键它考的是递归不是递推就导致这个忘的一干二净的内容错了俩(6分啊o(╥﹏╥)o)就差这两题就进复赛了(;へ:)
汉诺塔大家应该也知道,这里只说解法(还是懒......):
假设是n个盘子从左边柱子移动到右边柱子的移动次数,当n = 1时,
,当n = 2时,
......即当左边柱子有n
个盘子时,需要先借助右边柱子把上边n - 1个盘子全部移动到中间柱子上,然后把第n个盘子移到右边柱子,然后将中间柱子上的n - 1个盘子借助左边柱子移动到右边柱子上,以此类推,直至全部移动完毕。
经过一顿猛如虎的计算,最后啥都没算出来,结束,886
开个玩笑。
其实只需移动次就可以了(其实感觉递归更好写......下篇再说)
那么其实就是很简单的两项:
没了。
3.平面分割
觉得大家都知道,直接讲:
算了,先说一下吧。
假设有n条封闭曲线画在一个平面上,而任何两条都恰好交于两点,且任意三条不相交于一点,求将平面割成了几块。
好的,不管你听没听懂,直接看:
假设是n条封闭曲线分割出的区域个数,简单画一下就可以看出(懒得画)
,
,
......
好的公式出:。当然只是观察不一定对,证明交给你们哈,不想证明了,(懒得证),反正就是对的,边界
行了。
至于编的话自己试试下面这道题:
同一平面内的n条直线中,已知有x条曲线()相交于同一点,则这n条直线最多能把这个平面分成几个不同的区域?
4.Catalan数
这是在计算凸多边形的不同对角三角形剖分的个数问题得到的,出现在组合计数问题里面。
题目给了,直接解答(还是懒,懒神在世):
假设表示凸n边形的拆分方案总数。 那么经过一系列复杂的推算得出来凸n边形的拆分总数为
(超复杂所以到时候单独出个小作品讲这玩意儿的推理),边界
当然用搜索也可以但效率和复杂度嘛,自己看吧。
反正就这样吧,也是总算结束了。
小结
递推是竞赛中超级重要的算法之一,也是基本必考,因此仔细阅读一下(特别是要考的),还有,小编打得手都快废了,请大家不要吐槽,有问题找我改,谢谢大家!