递推问题

兔子繁殖问题

编写程序:如果有一对小兔子,每个月都剩下一对小兔,而所生下的每一对的小兔子,在出生后的第三个月也都能生下一对小兔。那么,由一对兔子开始,满一年时一种可以繁殖成多少对兔子?

分析:

1.我们以f(n)来表示第n个月的兔子对数,第n个月的小兔子对数有两部分,一部分为成年兔子对数,一部分为幼年兔子对数。
本月的成年兔子对数等于上个月的成年兔子对数加上个月幼年兔子的对数;
本月的幼年兔子是上个月的成年兔子生的,所以本月的幼年兔子对数就是上个月的成年兔子的对数,而根据上面分析,我们又得出上个月的成年兔子的对数等于上上个月的成年兔子的对数加幼年兔子的对数。
由此,我们得到 f(n) = f(n-1) + f(n-2)。

代码

#include<iostream>
using namespace std;

int f(int n) {
    switch (n) {
        case 1: return 1;
        case 2: return 2;
        default: return f(n - 1) + f(n - 2);
    }
    return 0;
}

int main(void) {
    int n;
    while (scanf("%d", &n) != EOF) {
        printf("%d\n", f(n));
    }
    return 0;
}
ydqun@VM-0-9-ubuntu donggui % gcc test.c                                        [0]
ydqun@VM-0-9-ubuntu donggui % ./a.out                                           [0]
6
13

思考:当n=40时,用上述程序求解会出现什么问题?n=60呢?

答:会出现以下两个问题
一:程序运行效率问题
二:程序计算结果超过整型表示范围,结果溢出出错

怎么解决?

解决问题一

其实,兔子繁殖问题是一种递推问题,递推有两种方向,一种是正向,一种是逆向
由 f(n) -> f(n - 1)方向求解是正向
f(1), f(2) -> f(3) -> f(4) -> ... -> f(n)逆向
逆向可以用循环去实现

#include<iostream>
using namespace std;

int main(void) {
    int n;
    while (scanf("%d", &n) != EOF) {
        int fn_1 = 0, fn_2 = 0, fn = 1;
        for (int i = 0; i < n; i++) {
            fn_2 = fn_1;
            fn_1 = fn;
            fn = fn_1 + fn_2;
        }
        printf("%d\n", fn);
    }
    return 0;
}

解决问题二

大整数去解决。

总结

一、递推问题的求解套路

1.确定递推状态,一个数字符号+一个数学符号的语义解释
2.确定递推状态,推导递推状态符号的自我表示方法
3.程序实现,(递归+记忆化/循环实现)

确定递推状态

注意:这是学习递推问题的重中之重。学习确定递推状态的技巧。
f(x) = y
y:问题中的求解量,也是我们所谓说的因变量
x:问题中直接影响求解量的部分,也是我们所谓说的自变量
本质:就是寻找问题中的自变量与因变量

推导递推公式

本质:分析状态中的容斥关系
f(n) = f(n - 1) + f(n - 2)
f(n - 1),代表n-1个月的兔子数量,恰巧等于第n个月的成年兔子数量
f(n - 2),代表n-2个月的兔子数量,恰巧等于第n个月的幼年兔子数量
实际的推导思路如下:
f(n) -->第n月的成年兔子数量 + 第n月的幼年兔子数量 --> f(n - 1) + f(n - 2)
所谓的推导递推公式,就是推导上面的这两句话的内容。

程序实现

上面的程序。

练习题1:爬楼梯

一个人每次只能走2节楼梯或者3节楼梯,问走到第N节篓子一共有多少种方法。

1、确定递推状态

f(n)表示上到第n阶楼梯的方法总数
子变量:上楼梯的阶数 n
因变量:方法数 f(n)

2、确定递推公式

利用容斥原理来分析。
f(n)表示上到第n阶楼梯的方法总数,然后上楼梯的方式有2步上和3步上,所以到第n阶楼梯的方法可以由最后一次在第n-2阶楼梯或最后一次在第n-3阶楼梯爬上来,故得到f(n) = f(n - 2) + f(n - 3),其中f(n - 2)又表示上到第n-2阶楼梯的总方法数,f(n - 3)又表示上到第n-3阶楼梯的总方法数,这样我们就可以一直往下递推。

3、程序实现

练习题2:钱币问题

现在给你1、2、5、10、20、50、100、200元若干张,问你想要凑足N元钱,一共有多少种不同的方法?
注意(1,1,2)和(1,2,1)属于同一种方法。

思考:凑到目标金额的方法总数受到什么影响?

1.目标金额;2使用钱币的种类。

1、确定递推状态

f(n) (m)代表使用前n种钱币凑到目标金额为m的方法总数。

2、推导递推公式

利用容斥原理,我们可以把f(n)(m) 分为没用第n种钱币和一定用了第n种钱币这两部分。
没用第n种钱币:f(n - 1)(m) --->又可以转换成了使用n-1种钱币凑到目标金额为m的方法总数。
一定用了第n种钱币:f(n)(m-val[n]) (val是一个数组,每个元素表示是第n种钱币的面额。)
f(n)(m) = f(n - 1)(m) + f(n)(m-val[n])

练习题3:墙壁涂色

给一个环形的墙壁,颜色一共三种,分别是红、黄、蓝,墙壁被竖直划分成wallsize个部分,相邻的部分颜色不能相同。请你写出函数paintWallCounts计算出一共有多少种给墙壁上色的方法。

思路:成环先转化为非成环,然后再挑出首位颜色不一样会的情况。

解法1

1、确定递推状态

因变量:方法总数
自变量:墙壁被分成的数量
解题技巧:头部颜色,尾部颜色
f(n, i, j)n块墙壁被染成头部为第i种颜色,尾部为第j种颜色的方法总数。

推导递推公式

分析

因为第n块墙壁涂第j种颜色,所以第n-1块墙壁不能涂第j种颜色。
f(n, i, j) = ∑f(n-1, i, k) | k != j
答案:∑∑f(n, i, j) | i != j

解法2

1.确定递推状态

f(n, j)代表n块墙壁,第一块涂第0种颜色,最后第一块涂第j种颜色的方案总数

2.推导递推公式

f(n, j) = ∑f(n, k) | j != k
(f(n, 1) + f(n, 2)) * 3

解法3:

1.确定递推状态

f(n)代表首位颜色不同的方案总数

f(n) = 2 * f(n - 2) + f(n - 1)

posted @ 2022-09-13 15:42  ydqun  阅读(50)  评论(0编辑  收藏  举报