算法3:递推算法

零.【摘要】:在这个专刊里,我会把所有算法都讲一遍,这章讲了递推算法的原理和题目。

一.【递推算法】:

递推算法也被称为迭代算法,是一种通过利用已知的初始条件和递推关系来计算未知项的算法。

递推算法的基本思想是根据已知的起点,通过递推公式来推导出下一个未知项的值。常见的递推关系可以是线性关系、递归关系等。递推算法通常以循环的形式进行,并且每一步都依赖于前一步的结果。

递推算法的优点是可以简洁地表达问题的求解过程,并且适用于一些数值计算问题,特别是当问题具有明显的规律和线性关系时。递推算法的时间复杂度通常较低,运行效率较高。

递推算法的示例包括斐波那契数列的计算、阶乘的计算等。在斐波那契数列中,递推算法可以通过以下递推公式来计算第n项的值:F(n) = F(n-1) + F(n-2),其中F(0) = 0,F(1) = 1。通过递推算法,可以依次计算出斐波那契数列的每一项的值。

总的来说,递推算法是一种基于已知条件和递推关系,通过循环和迭代计算未知项的值的算法。它通常适用于一些具有规律和线性关系的数值计算问题,并且具有高效率和简洁性的优势。

二.【操作步骤】:

  1. 定义递推函数

    • 定义一个函数来表示递推算法的实现。
    • 在函数的参数中确定递推函数所依赖的参数,例如递推的起始值、递推的步长等。
    • 在函数中定义好递推的初始值。
  2. 进行递推计算

    • 使用循环结构(如for循环或while循环)来进行递推的迭代计算。
    • 在每一次迭代中,根据递推方程计算递推的下一个值,并更新递推的状态。
  3. 返回计算结果

    • 在递推函数中通过return语句返回最终的计算结果。

三.【例题讲解】:
 

1:昆虫繁殖


时间限制: 1000 ms         内存限制: 65536 KB
提交数: 41726     通过数: 20779

【题目描述】

科学家在热带森林中发现了一种特殊的昆虫,这种昆虫的繁殖能力很强。每对成虫过x�个月产y�对卵,每对卵要过两个月长成成虫。假设每个成虫不死,第一个月只有一对成虫,且卵长成成虫后的第一个月不产卵(过x�个月产卵),问过z�个月以后,共有成虫多少对?0≤x≤20,1≤y≤20,X≤z≤500≤�≤20,1≤�≤20,�≤�≤50。

【输入】

x,y,z的数值。

【输出】

过z个月以后,共有成虫对数。

【输入样例】

1 2 8

【输出样例】

37

提交 统计信息 提交记录

 【算法思路】:

根据题目描述,我们可以使用递推算法来解决这个问题。

首先,我们假设当前月份为第1个月,有1对成虫。

然后,我们使用循环来进行递推计算。每个月,根据题目给出的条件,我们可以得知:

  • 在第 x+1 个月,每对成虫会产生 y 对卵。
  • 在第 x+2 个月,每对卵会长成成虫。

所以,在每个月中,我们可以得到下一个月的成虫对数(即当前月的成虫对数乘以 y)。

最后,我们在第 z 个月结束后,得到的成虫对数就是最终结果。

【代码实现】:

#include<iostream>
using namespace std;
int main()
{
	long long a[101], b[101];//a[i]:第i个月有多少对虫  b[i]:第i个月出生的卵的数量 
    int x, y, z;
    cin >> x >> y >> z;
    for(int i = 1; i <= x; i++)//前x个月只有第一对幼年虫 
    {
        a[i] = 1;
        b[i] = 0;
    }
    for(int i = x + 1; i <= z + 1; i++)//求第z个月后,即第z+1个月 
    {
        b[i] = a[i-x]*y; 
        a[i] = a[i-1]+b[i-2];
    }
    cout << a[z+1] << endl;
    return 0;
}

我们使用 long long 类型来存储结果,以确保可以处理较大的数值。

2:山区建小学


时间限制: 1000 ms         内存限制: 65536 KB
提交数: 8330     通过数: 6027

【题目描述】

政府在某山区修建了一条道路,恰好穿越总共m�个村庄的每个村庄一次,没有回路或交叉,任意两个村庄只能通过这条路来往。已知任意两个相邻的村庄之间的距离为di��(为正整数),其中,0<i<m0<�<�。为了提高山区的文化素质,政府又决定从m�个村中选择n�个村建小学(设0<n≤m<5000<�≤�<500)。请根据给定的m�、n�以及所有相邻村庄的距离,选择在哪些村庄建小学,才使得所有村到最近小学的距离总和最小,计算最小值。

【输入】

第1行为m和n,其间用空格间隔

第2行为m−1 个整数,依次表示从一端到另一端的相邻村庄的距离,整数之间以空格间隔。

例如:

10 3
2 4 6 5 2 4 3 1 3

表示在1010个村庄建33所学校。第11个村庄与第22个村庄距离为22,第22个村庄与第33个村庄距离为44,第33个村庄与第44个村庄距离为66,...,第99个村庄到第1010个村庄的距离为33。

【输出】

各村庄到最近学校的距离之和的最小值。

【输入样例】

10 2
3 1 3 1 1 1 1 1 3

【输出样例】

18

提交 统计信息 提交记录

 【算法思路】

  • 首先,我们使用vector<int> distances来存储每个村庄到最近学校的距离,初始值设为INT_MAX,表示无穷大。
  • 然后,我们从输入中读取m和n,m表示村庄的数量,n表示学校的数量。
  • 接下来,我们使用for循环读取学校的位置,并将学校位置的距离设为0,表示该村庄已经有学校。
  • 然后,我们使用两个for循环,分别从左往右和从右往左依次更新距离。
  • 我们从左往右的循环中,通过比较当前村庄的距离和前一个村庄的距离+1的值,更新最小距离。
  • 我们从右往左的循环中,通过比较当前村庄的距离和后一个村庄的距离+1的值,更新最小距离。
  • 最后,我们通过一个循环输出每个村庄到最近学校的距离。

【代码实现】:

#include <iostream>
#include <vector>
#include <climits>
using namespace std;

int main() {
    int m, n;
    cin >> m >> n;

    // distances存储每个村庄到最近学校的距离
    vector<int> distances(m, INT_MAX);
    
    // 初始化边界条件,如果村庄有学校,则距离为0
    for (int i = 0; i < n; i++) {
        int school;
        cin >> school;
        distances[school] = 0;
    }
    
    // 从左往右依次更新距离
    for (int i = 1; i < m; i++) {
        distances[i] = min(distances[i], distances[i-1] + 1);
    }
    
    // 从右往左依次更新距离
    for (int i = m-2; i >= 0; i--) {
        distances[i] = min(distances[i], distances[i+1] + 1);
    }
    
    // 输出每个村庄到最近学校的距离
    for (int i = 0; i < m; i++) {
        cout << distances[i] << " ";
    }
    
    return 0;
}

注意:

  • 在上述代码中,我们使用了vector来动态分配存储空间,而不使用静态数组和动态数组来避免固定内存大小的限制。vector提供了动态大小的数组功能,可以根据需要自动调整大小。
  • 使用vector可以更方便地处理动态数据,并且可以避免数组的大小限制。

3:【例3.5】位数问题


时间限制: 1000 ms         内存限制: 65536 KB
提交数: 31281     通过数: 17070

【题目描述】

在所有的N位数中,有多少个数中有偶数个数字33?由于结果可能很大,你只需要输出这个答案对1234512345取余的值。

【输入】

读入一个数N(N≤1000)。

【输出】

输出有多少个数中有偶数个数字33。

【输入样例】

2

【输出样例】

73

提交 统计信息 提交记录

 【算法思路】:

这道题目要求在所有的N位数中,有多少个数中有偶数个数字3,并要求对结果取余。

我们可以分析一下可以出现多少种情况。对于一个N位数,每一位都可以取0-9之间的数字。那么对于有偶数个数字3的数来说,可能出现的情况有两种:

  1. 所有的3都出现偶数次数。也就是说,对于N位数的每一位来说,要么是0-2、4-9中的一个数字,要么是3出现2、4、6...次。我们可以分两种情况来计算:

    • 如果N为奇数,那么就有(N - 1) / 2种数字选择去出现3的偶数次。
    • 如果N为偶数,那么就有N / 2种数字选择去出现3的偶数次。
  2. 某一个3出现奇数次,其他3出现偶数次。也就是说,对于N位数的每一位来说,除了某一位是3出现奇数次以外,其他位要么是0-2、4-9中的一个数字,要么是3出现2、4、6...次。我们可以分两种情况来计算:

    • 如果N为奇数,那么就有(N - 1) / 2种数字选择去出现3的偶数次,再乘以9种可能的位置选择去出现奇数次。
    • 如果N为偶数,那么就有N / 2种数字选择去出现3的偶数次,再乘以9种可能的位置选择去出现奇数次。

综上所述,我们可以计算这两种情况的总和,即第一种情况的总数加上第二种情况的总数。最后将结果对12345取余即可。

【代码实现】:

#include <iostream>
using namespace std;

int main() {
    int N;
    cin >> N;

    // 计算第一种情况的总数
    int count1 = (N % 2 == 0) ? N / 2 : (N - 1) / 2;

    // 计算第二种情况的总数
    int count2 = (N % 2 == 0) ? N / 2 : (N - 1) / 2;
    count2 *= 9;

    // 计算总和并对12345取余
    int result = count1 + count2;
    result %= 12345;

    cout << result << endl;

    return 0;
}

4:流感传染


时间限制: 1000 ms         内存限制: 65536 KB
提交数: 27798     通过数: 15230

【题目描述】

有一批易感人群住在网格状的宿舍区内,宿舍区为n*n的矩阵,每个格点为一个房间,房间里可能住人,也可能空着。在第一天,有些房间里的人得了流感,以后每天,得流感的人会使其邻居传染上流感,(已经得病的不变),空房间不会传染。请输出第m天得流感的人数。

【输入】

第一行一个数字n,n不超过100,表示有n*n的宿舍房间。

接下来的n行,每行n个字符,’.’表示第一天该房间住着健康的人,’#’表示该房间空着,’@’表示第一天该房间住着得流感的人。

接下来的一行是一个整数m,m不超过100。

【输出】

输出第m天,得流感的人数。

【输入样例】

5
....#
.#.@.
.#@..
#....
.....
4

【输出样例】

16

提交 统计信息 提交记录

 【算法思路】:

我们可以定义一个二维数组dp来保存每一天传染的人数。dp[i][j]表示第i天的时候,房间(i, j)中得流感的人数。

初始状态,第一天的时候,如果房间中是得流感的人,那么dp[i][j]的初始值为1,否则为0。

接下来,我们可以采用动态规划的方式来递推每一天的感染人数。对于第i天的时候,房间(i, j)中得流感的人数取决于其前一天的时候周围四个方向的房间得流感的人数之和(dp[i-1][j] + dp[i+1][j] + dp[i][j-1] + dp[i][j+1])

最后,我们可以遍历第m天的感染人数,并累加得流感的人数,输出结果。

【代码实现】:

#include <iostream>
#include <vector>
using namespace std;

int main() {
    int n;
    cin >> n;

    // 定义宿舍区的状态
    vector<vector<int>> grid(n, vector<int>(n, 0));
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            char c;
            cin >> c;
            if (c == '@') {
                grid[i][j] = 1;
            }
        }
    }

    int m;
    cin >> m;

    // 定义dp数组保存每一天的感染人数
    vector<vector<int>> dp(n, vector<int>(n, 0));

    // 初始化第一天的感染人数
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            if (grid[i][j] == 1) {
                dp[i][j] = 1;
            }
        }
    }

    // 动态规划递推每一天的感染人数
    for (int k = 2; k <= m; k++) {
        vector<vector<int>> temp(dp);

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == 0) {
                    int count = 0;
                    if (i - 1 >= 0 && grid[i-1][j] == 1) {
                        count++;
                    }
                    if (i + 1 < n && grid[i+1][j] == 1) {
                        count++;
                    }
                    if (j - 1 >= 0 && grid[i][j-1] == 1) {
                        count++;
                    }
                    if (j + 1 < n && grid[i][j+1] == 1) {
                        count++;
                    }
                    dp[i][j] = count;
                }
            }
        }

        // 更新grid数组为当前dp数组的值
        grid = temp;
    }

    // 统计第m天的感染人数
    int result = 0;
    for (vector<int>& row : dp) {
        for (int num : row) {
            result += num;
        }
    }

    cout << result << endl;

    return 0;
}

5:移动路线


时间限制: 1000 ms         内存限制: 65536 KB
提交数: 19807     通过数: 15089

【题目描述】

X桌子上有一个m行n列的方格矩阵,将每个方格用坐标表示,行坐标从下到上依次递增,列坐标从左至右依次递增,左下角方格的坐标为(1,1),则右上角方格的坐标为(m,n)。

小明是个调皮的孩子,一天他捉来一只蚂蚁,不小心把蚂蚁的右脚弄伤了,于是蚂蚁只能向上或向右移动。小明把这只蚂蚁放在左下角的方格中,蚂蚁从

左下角的方格中移动到右上角的方格中,每步移动一个方格。蚂蚁始终在方格矩阵内移动,请计算出不同的移动路线的数目。

对于1行1列的方格矩阵,蚂蚁原地移动,移动路线数为1;对于1行2列(或2行1列)的方格矩阵,蚂蚁只需一次向右(或向上)移动,移动路线数也为1……对于一个2行3列的方格矩阵,如下图所示:

(2,1)(2,2)(2,3)
(1,1)(1,2)(1,3)

蚂蚁共有3种移动路线:

路线1:(1,1) → (1,2) → (1,3) → (2,3)

路线2:(1,1) → (1,2) → (2,2) → (2,3)

路线3:(1,1) → (2,1) → (2,2) → (2,3)

【输入】

输入只有一行,包括两个整数m和n(0 < m+n ≤ 20),代表方格矩阵的行数和列数,m、n之间用空格隔开。

【输出】

输出只有一行,为不同的移动路线的数目。

【输入样例】

2 3

【输出样例】

3

提交 统计信息 提交记录

【算法思路】:

我们可以定义一个二维数组dp来保存不同的移动路线的数目。dp[i][j]表示蚂蚁从左下角方格到达(i, j)方格的移动路线数目。

首先,对于第一行和第一列的方格,蚂蚁只能一直向右或向上移动,因此其移动路线数目都为1。

接下来,我们可以使用动态规划的方式来递推每个方格的移动路线数目。对于方格(i, j),蚂蚁可以从左边的方格(i-1, j)到达,也可以从下面的方格(i, j-1)到达。因此,蚂蚁到达(i, j)的移动路线数目等于到达(i-1, j)(i, j-1)方格的移动路线数目之和。

最终,我们可以得到右上角方格的移动路线数目,输出结果。

【代码实现】:

#include <iostream>
#include <vector>
using namespace std;

int main() {
    int m, n;
    cin >> m >> n;

    // 定义dp数组保存不同的移动路线数目
    vector<vector<long long>> dp(m+1, vector<long long>(n+1, 0));

    // 初始化第一列的移动路线数目
    for (int i = 1; i <= m; i++) {
        dp[i][j] = dp[i-1][j] + dp[i][j-1];
    }

    // 初始化第一行的移动路线数目
    for (int j = 1; j <= n; j++) {
        dp[1][j] = 1;
    }

    // 动态规划递推每个方格的移动路线数目
    for (int i = 2; i <= m; i++) {
        for (int j = 2; j <= n; j++) {
            dp[i][j] = dp[i-1][j] + dp[i][j-1];
        }
    }

    // 输出右上角方格的移动路线数目
    cout << dp[m][n] << endl;

    return 0;
}

创作不易,点个👍,观个注吧。

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值