递推算法精讲|普及组算法

递推算法详解与应用

例1

上台阶
N N N级的台阶,你一开始在底部,每次可以向上迈最多 2 2 2级台阶(最少 1 1 1级),问到达第 N N N级台阶有多少种不同方式。

审题

一开始在第0层,每次要么跨1层,要么跨2层,利用加法原理:

  • 到第1层有1种方法:跨1层;
  • 到第2层有2种方法:一层一层走&直接跨两层;

这2个条件是我们预先知道的,后续的就很容易计算出来了。在走到第3层之前有2种情况,第一种情况我们是从第1层跨两层上来的,第二种情况我们是从第2层跨一层上来的。也就是说,求走到第3层的方式数就是求走到第1层和第2层的方式数之和。如果用 a i a_i ai记录走到第 i i i层的方式数,那么计算第3层方式数的算式就是:
a 3 = a 1 + a 2 a_3=a_1+a_2 a3=a1+a2
其实,也可以这样说:走到第3层的方式数是前两层的方式数之和。那么肯定不光第三层可以这样算,第4层和第5层应该也可以,第10层可以……这个楼梯的任意一层都可以这样算。如果想计算第 n n n层呢?也是这个方法,第 n n n层的方式数就是第 n − 1 n-1 n1层和第 n − 2 n-2 n2层的方式数之和。可以用以下算式表示:
a n = a n − 1 + a n − 2 a_n=a_{n-1}+a_{n-2} an=an1+an2
因为每一项都依赖它的前2项,所以算的时候要自底向上计算:从第3层开始一直这样算到第 n n n层。看到这停一下,请先把上面的内容消化消化。

如果你完全理解了上面的内容,不难写出以下代码:

#include <iostream>
using namespace std;
int main() {
    int n;
    long long a[10005];
    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];
    return 0;
}

递推的基本思想

在上一题中,我们使用了一种新算法,这种算法叫做递推法,它的基本思想是利用已经计算出的结果来推导新的结果从而避免重复计算提高效率。递推法有正推反推两种形式:

  • 正推就是从前向后递推,已知小规模问题的解递推到大规模问题的解
  • 反推就是从后向前递推,已知大规模问题的解递推到小规模问题的解

解决递推问题的步骤

解决递推问题的步骤如下:

1.定义初始状态

我们把递推过程中计算出的每一项称为一个状态,通常初始状态是题目中已经告诉我们的或是我们自己根据题干很容易推导出的一个量,后续的所有计算都是建立在初始状态的基础之上的。例如,在“上台阶”中,初始状态是第一层和第二层的方式数,这两个值是我们根据逻辑直接得到的,而不是用式子计算出来的。

找到了初始状态,再对它们进行定义。

2.找到递推关系式

要做好递推,这一步非常关键。递推关系式就是找出未知状态和已知状态之间的关系,并用一个宽泛的数学式子来表示这种关系。这个关系式应该对于任意一个状态都有效。

在例题1中,这种关系是“每一层的方式数就是前2层的方式数之和”,因此递推关系式就是 a n = a n − 1 + a n − 2 a_n=a_{n-1}+a_{n-2} an=an1+an2

例2

填数 在n个格子内填数,每个格子内只能填1,2,3中的一个,要求任意相邻的两个格子内的和为偶数,请你求出n个格子的时候有几种填法。

定义初始状态

先找初始状态,初始状态题目中的样例已经告诉我们了,即只有2个格子时的填法数。如果用 a i a_i ai来记录当有 i i i个格子时的填法数,那么 a 2 = 5 a_2=5 a2=5

找到递推关系式

在你找出了和为偶数的组合情况有1+3,3+1,1+1,3+3,2+2这5种以后,不难发现:如果前面的格子是填奇数,那么奇数后面还是奇数,有1或3这两种情况,则前 n n n个格子的填法数是前 n − 1 n-1 n1个格子的2倍。递推关系式为:
a n = a n − 1 × 2 a_n=a_{n-1}×2 an=an1×2
如果考虑偶数情况(只有1种情况,因为2后面只能填2),那么乘以2之前要把偶数的一种情况去掉,乘完了以后再加上这一种情况。递推关系式:
a n = ( a n − 1 − 1 ) × 2 + 1 a_n=(a_{n-1}-1)×2+1 an=(an11)×2+1

完整代码

#include <iostream>
using namespace std;
long long a[10005];
int main() {
    int n; 
    cin >> n;
    a[2] = 5; // 初始状态
    for (int i = 3; i <= n; i++) {
        a[i] = (a[i - 1] - 1) * 2 + 1; // 递推关系式
    }
    cout << a[n];
    return 0;
}

例3

Gold King上色 青青草原上有一个神秘的地方,叫做女娲谷。谷里有一排排长度不一的石格子,相传是女娲补天之后留下来的,并且还有一个传说一起流传下来,如果能用三种颜色把格子都填色,就能得到女娲的祝福。青青草原的人每年都派人去尝试,今年轮到 GoldKing,GoldKing也为了锻炼自己,背着三包颜料就出发了。
刚进入谷口,就有一行若隐若现的文字显示出来:“用三种颜色对一行格子涂色,要求相邻两个格子的颜色不同并且头尾的颜色也不同,这里注意只有一个格子的时候,既是头也是尾。”,这里GoldKing有一个疑惑如果当格子数为n时,有多少种不同的涂法呢,需要你帮忙解决一下?

定义初始状态

题目说了,1个格子和0个格子的填法数都为0,但这道题光有这两个条件还不够。顺着往下想,当有2个格子时,第一格就是头,第二格就是尾,根据题意,这两个格子的颜色应该互不相同。从3种颜色中选2种颜色组合在一起,有红绿、红黄、绿红、绿黄、黄红、黄绿六种方案。用 a i a_i ai表示有 i i i个格子时的填法数,则 a 0 = 0 , a 1 = 0 , a 2 = 6 a_0=0,a_1=0,a_2=6 a0=0,a1=0,a2=6

找到递推关系式

当有3个格子时,第一个格子是头,第三个格子是尾,第三个格子既不能和第一个格子相同,也不能和与它相邻的第二个格子相同,那第三个格子只有除去前两个格子的剩下的一种颜色可填。所以2个格子的填法数是6,3个格子的填法数也是6。

当有4个格子时,要考虑2种情况。第一种情况,是直接在符合要求的3个格子之后加上一个格子。第一个格子是头,第四个格子是尾,第四个格子既不能和第一个格子相同,也不能和与它相邻的第三个格子相同,那第四个格子只有除去第一个格子和第三个格子的剩下一种颜色可填,则4个格子的填法数和3个格子一样,也是6。关系式为:
a n = a n − 1 a_n = a_{n-1} an=an1

第二种情况,因为增加到4格后,第三格已经不是尾了,可以和第一格相同。第四个格子仍然既不能和第一个格子相同,也不能和与它相邻的第三个格子相同,但第一格和第三格实际上是同一种颜色,所以第四格可以填除去这一种颜色的其余两种颜色。而第一格确定了,第三格就不用考虑了,全部4格的填法数只跟去掉第三格的前2格有关,并且是前2格填法数的2倍。关系式为:
a n = a n − 2 × 2 a_n=a_{n-2}×2 an=an2×2
最终的填法数就是这两种情况的填法数之和,递推关系式:
a n = a n − 1 + a n − 2 × 2 a_n=a_{n-1}+a_{n-2}×2 an=an1+an2×2

完整代码

#include <iostream>
using namespace std;
long long a[60];
int main() {
    a[0] = 0, a[1] = 0, a[2] = 6;
    int n;
    cin >> n;
    for (int i = 3; i <= n; i++) {
        a[i] = a[i - 1] + a[i - 2] * 2;
    }
    cout << a[n];
    return 0;
}

那么本帖就到这里结束了,谢谢各位的观看

如有错误,欢迎指正~

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值