L2-算法基础-第07课 递推
1, 2, 4, 8, 16 …… 数列的下一项是什么?
1, 2, 3, 5, 8 …… 数列的下一项是什么?
递推
递推就是指从已知的初始条件出发,依据某种递推关系,逐次推出所要求的各中间结果及最后结果。
可用递推算法求解的问题一般有以下两个特点:
问题可以划分成多个状态;
除初始状态外,其它各个状态都可以用固定的递推关系式来表示.
利用递推算法解决问题,需要做好以下四个方面的工作:
确定递推变量 应用递推算法解决问题,要根据问题的具体实际设置递推变量。递推变量可以是简单变量,也可以是一维或多维数组。从直观角度出发,通常采用一维数组。
建立递推关系 递推关系是指如何从变量的前一些值推出其下一个值,或从变量的后一些值推出其上一个值的公式(或关系)。递推关系是递推的依据,是解决递推问题的关键。有些问题,其递推关系是明确的,大多数实际问题并没有现成的明确的递推关系,需根据问题的具体实际,通过分析和推理,才能确定问题的递推关系。
确定初始(边界)条件 对所确定的递推变量,要根据问题最简单情形的数据确定递推变量的初始(边界)值,这是递推的基础。
对递推过程进行控制 递推过程不能无休止地重复执行下去。递推过程在什么时候结束,满足什么条件结束,这是编写递推算法必须考虑的问题。递推过程的控制通常可分为两种情形:一种是所需的递推次数是确定的值,可以计算出来;另一种是所需的递推次数无法确定。对于前一种情况,可以构建一个固定次数的循环来实现对递推过程的控制;对于后一种情况,需要进一步分析出用来结束递推过程的条件。
递推通常由循环来实现,一般在循环外确定初始(边界)条件,在循环中实施递推。
递推法从递推方向可分为顺推与倒推。
所谓顺推法是从已知条件出发,通过递推关系逐步推算出要解决的问题的结果的方法。
所谓倒推法,就是在不知初始值的情况下,经某种递推关系而获知了问题的解或目标,从这个解或目标出发,采用倒推手段,一步步地倒推到这个问题的初始情况。
一句话概括:顺推是从条件推出结果,倒推从结果推出条件。
P5743 【深基7.习8】猴子吃桃
题目描述
一只小猴买了若干个桃子。第一天他刚好吃了这些桃子的一半,又贪嘴多吃了一个;接下来的每一天它都会吃剩余的桃子的一半外加一个。第 n(n≤20) 天早上起来一看,只剩下 1 个桃子了。请问小猴买了几个桃子?
输入格式
无
输出格式
无
输入输出样例
输入 #1
4
输出 #1
22
分析
倒推来分析, 最后一天是1颗桃子. 第i天的桃子是f[i]的话 第i+1天吃的是i天桃子的一半加1个, 所以剩下的桃子数:f[i+1] = f[i] / 2 - 1
递推公式: f[i] = (f[i+1] + 1 ) * 2;
初始条件: f[n] = 1;
终止条件: i = 1. 循环n-1次.
#include
using namespace std;
int n, k;
int main(){
cin >> n;
k = 1;
while(n > 1) {
k = 2 * (k + 1);
n--;
}
cout << k;
return 0;
}
常见递推关系
斐波那契数列(兔子数列)
斐波那契数列又称黄金分割数列、因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波那契数列以如下被以递推的方法定义:F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)在现代物理、准晶体结构、化学等领域,斐波纳契数列都有直接的应用,为此,美国数学会从1963年起出版了以《斐波纳契数列季刊》为名的一份数学杂志,用于专门刊载这方面的研究成果 。
一般而言,兔子在出生两个月后,就有繁殖能力,一对兔子每个月能生出一对小兔子来。如果所有兔子都不死,那么一年以后可以繁殖多少对兔子?
幼仔对数=前月成兔对数
成兔对数=前月成兔对数+前月幼仔对数
总体对数=本月成兔对数+本月幼仔对数
递推分析
递推变量: f[n]: 一维数组, 存放兔子对数. 下标表示月份.
初始条件: f[1] = 1; f[2] = 1;
递归公式: f[n] = f[n-1] + f[n-2]
P1255 数楼梯
题目描述
楼梯有 N 阶,上楼可以一步上一阶,也可以一步上二阶。编一个程序,计算共有多少种不同的走法。
输入格式
一个数字,楼梯数。
输出格式
输出走的方式总数。
输入输出样例
输入 #1复制 4
输出 #1复制 5
说明/提示 对于 60% 的数据,N≤50;对于 100% 的数据,N≤5000。
分析
高精 + 斐波那契数列
参考代码
#include <bits/stdc++.h>using namespace std;
int n, a[5006], b[5006], c[5006];
int *f1, *f2, *f3;
void add(int *f1, int *f2, int *f3)
{
int len = max(f1[0], f2[0]);
int x = 0;
for(int i = 1; i <= len; i++) {
f3[i] = f1[i] + f2[i] + x;
x = f3[i] / 10;
f3[i] %= 10;
}
if(x) {
len++;
f3[len] = x;
}
f3[0] = len;
}
int main()
{
scanf("%d", &n);
a[0] = 1;
a[1] = 1;
b[0] = 1;
b[1] = 1;
c[0] = 1;
int * tmp;
f1 = a;
f2 = b; f3 = c;
for(int i = 2; i <= n; i++) {
add(f1, f2, f3);
if(i < n) {
tmp = f1;
f1 = f2;
f2 = f3;
f3 = tmp;
}
}
//printf("%d\n", f3[0]);
if(n == 0)
f3 = c;
else if(n == 1)
f3 = a;
for(int i = f3[0]; i >= 1; i--)
printf("%d", f3[i]);
return 0;
}
卡特兰数
P1044 栈
题目背景
栈是计算机中经典的数据结构,简单的说,栈就是限制在一端进行插入删除操作的线性表。
栈有两种最重要的操作,即 pop(从栈顶弹出一个元素)和 push(将一个元素进栈)。
栈的重要性不言自明,任何一门数据结构的课程都会介绍栈。宁宁同学在复习栈的基本概念时,想到了一个书上没有讲过的问题,而他自己无法给出答案,所以需要你的帮忙。
题目描述
宁宁考虑的是这样一个问题:一个操作数序列,1,2,\ldots ,n1,2,…,n(图示为 1 到 3 的情况),栈 A 的深度大于 nn。
现在可以进行两种操作,
将一个数,从操作数序列的头端移到栈的头端(对应数据结构栈的 push 操作)
将一个数,从栈的头端移到输出序列的尾端(对应数据结构栈的 pop 操作)
使用这两种操作,由一个操作数序列就可以得到一系列的输出序列,下图所示为由 1 2 3 生成序列 2 3 1 的过程。
(原始状态如上图所示)
你的程序将对给定的 n,计算并输出由操作数序列 1, 2, 3, ... ,n 经过操作可能得到的输出序列的总数。
输入格式
输入文件只含一个整数 n(1≤n≤18)。
输出格式
输出文件只有一行,即可能输出序列的总数目。
输入输出样例 输入 #1 3
输出 #1 5
分析
递推变量: f[n] 存放序列的总数目, 下标表示当前队元素个数-也是长度.
递推: f[1] = 1; f[2] = 2, f[3]=5.
递推公式: 假设当前考虑到第i个. 那么前i个元素得到的输出序列有多少个呢? 观察可知, 输出序列的个数和这个序列的元素个数有关. 当i这个元素,出栈时, 已经出栈的个数可能是0, 1, 2, ... i-1个, 剩下的还未出栈. 这些情况不会有重复, 所有总数目是这些情况的和. f[i] = f[0]*f[i-1] + f[1]*f[i-2] + ... + f[i-1]*f[0];
这就是传说中的卡特兰数.
参考代码
#include
using namespace std;
int n;
long long f[20];
int main(){
scanf("%d", &n);
f[0] = 1;
f[1] = 1;
for(int i = 2; i <= n; i++) {
for(int j = 0; j < i; j++) {
f[i] += f[i-j-1] * f[j];
}
}
printf("%d", f[n]);
return 0;
}
P1002 过河卒
题目描述
棋盘上 A 点有一个过河卒,需要走到目标 B点。卒行走的规则:可以向下、或者向右。同时在棋盘上 C 点有一个对方的马,该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此称之为“马拦过河卒”。
棋盘用坐标表示,A 点 (0,0)、B 点 (n, m),同样马的位置坐标是需要给出的。
现在要求你计算出卒从 A 点能够到达 B 点的路径的条数,假设马的位置是固定不动的,并不是卒走一步马走一步。
输入格式
一行四个正整数,分别表示 B 点坐标和马的坐标。
输出格式
一个整数,表示所有的路径条数。
输入输出样例 输入 #1复制 6 6 3 3
输出 #1复制 6
说明/提示 对于 100% 的数据,1≤n,m ≤20,0 ≤ 马的坐标 ≤20。
分析
递推变量 f[i][j]: 表示走到(i,j)点的路径数. i, j 表示点的坐标.
递推公式: 倒推: 走到(i,j)点只能从(i-1, j)或者(i, j-1)走过来. 这点和数楼梯是一样的. 所以有
f[i][j] = f[i-1][j] + f[i][j-1];
起始条件: f[0][0] = 1;
特殊处理: 马的位置和控制的点, 不能走过去, 所以经过这些点的路径数是0.
f[house_i][house_j] = 0;
参考代码
#include
using namespace std;
unsigned long long chess[26][26];
int ax, ay, bx, by;
int horse[][2]= {
{-2, 1}, {-2, -1}, {2, 1}, {2, -1},
{1, -2}, {1, 2}, {-1, 2}, {-1, -2}
};
int main()
{
int i, j;
scanf("%d %d %d %d", &ax, &ay ,&bx, &by);
ax+=1; ay+=1;
bx+=1; by+=1;
chess[bx][by] = 1;
for(i = 0; i < 8; i++) {
int tbx = bx + horse[i][0];
int tby = by + horse[i][1];
if(tbx < 1 || tby < 1)
continue;
if(tbx > ax || tby > ay)
continue;
chess[tbx][tby] = 1;
}
chess[1][0] = 1;
for(i = 1; i <= ax; i++) {
for(j = 1; j <= ay; j++) {
if(chess[i][j]) {
chess[i][j] = 0;
continue;
}
chess[i][j] = chess[i-1][j] + chess[i][j-1];
}
}
printf("%llu\n", chess[ax][ay]);
return 0;
}
题单
P5743 【深基7.习8】猴子吃桃
P5534 【XR-3】等差数列
P1255 数楼梯
P1192 台阶问题
P2807 三角形计数
P1990 覆盖墙壁
P1002 过河卒
P1044 栈
P1028 数的计算
P2437 蜜蜂路线
云帆优培公众号:
云帆优培老师联系方式:
云帆老师
微信:
云帆优培介绍