c++算法初级8——递推

news2024/10/3 2:14:12

c++算法初级8——递推

文章目录

  • c++算法初级8——递推
    • 递推
    • 递推思想的运用
      • 错位排序
      • 杨辉三角(二维递推)

递推

递推思想:

根据已有的东西一点点地推出未知的东西。

使用递推解题三步骤:

  • 数学建模
  • 找出递推式和初始条件
  • 写出代码。

张爽的青蛙(斐波那契)问题:地上有n个石头从左到右排成一排,张爽同学养的青蛙要从第一个石头跳到最后一个石头上,每次可以选择向右跳一格或者跳两格,问总共有多少种不同的走法?

递推表达式:设跳到第i格有 f ( i ) f(i) f(i)个跳法,则 f ( i ) = f ( i − 1 ) + f ( i − 2 ) f(i)=f(i-1)+f(i-2) f(i)=f(i1)+f(i2)
初始条件:f[1] = f[2] = 1。因为从1走到1只有一种方案(呆在原地不动),从1走到2也只有一种方案(走一格);
代码:

# include "bits/stdc++.h"
using namespace std;
const int MOD = 998244353; // 答案对998244353取模。
int k, f[1000010];

int main()
{
    cin >> k;
    f[1] = 1;
    f[2] = 1;
    for (int i = 3; i <= k; i++)
    {
        f[i] = (f[i - 1] + f[i - 2]) % MOD;
    }
    cout << f[k] << endl;
    return 0;
}

卡特兰数问题:由n对括号组成的括号序列,有多少种是合法的括号序列?答案对998244353取模。

什么是合法的括号序列?其定义如下:

空序列是合法的括号序列
如果A是合法的括号序列,那么(A)是合法的括号序列
如果A和B是合法的括号序列,那么AB也是合法的括号序列

简单通俗地讲,合法的括号序列就是:任何一个左括号都必须有与之对应的右括号,任何一个右括号都必须有与之对应的左括号。

比如:

()(()(()))是合法的括号序列
)(不是合法的括号序列,因为第一个右括号没有与之对应的左括号
(()))不是合法的括号序列,因为最后一个右括号没有与之对应的左括号

类似的,如果我们想用递推解决问题,我们就要找到递推式。首先开一个数组int f[n],用f[i]来表示i对括号能够组成多少种合法的括号序列。那么,怎么根据f[0], f[1], f[2], …, f[k-1]的值推出f[k]的值呢?

我们继续使用分类讨论的思想:由于合法括号序列的最后一个字符一定是右括号,不妨假设最终的括号序列长成这个样子:A(B)。其中,A和B都是合法括号序列(注意A和B可以是空序列)。

我们把最终的序列分成k种:

A由0对括号组成,B由k-1对括号组成,这样的序列有f[0] * f[k-1]种
A由1对括号组成,B由k-2对括号组成,这样的序列有f[1] * f[k-2]种
A由2对括号组成,B由k-3对括号组成,这样的序列有f[2] * f[k-3]种
……
A由m对括号组成,B由k-1-m对括号组成,这样的序列有f[m] * f[k-1-m]种
……
A由k-1对括号组成,B由0对括号组成,这样的序列有f[k-1] * f[0]种

由此,就得到了递推式
在这里插入图片描述
初始条件:
f(0)=1,,因为0对括号只能组成一种括号序列(空序列)
代码

# include"bits/stdc++.h"
using namespace std;
const int MOD = 998244353;
int n, f[100010];

int main() {
    cin >> n;
    f[0] = 1;
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j < i; j++) {
            f[i] = (f[i] + 1ll * f[j] * f[i - j - 1]%MOD) % MOD;
        }
    }
    cout << f[n] << endl;
    return 0;
}

时间复杂度 O ( n 2 ) O(n^2) O(n2)

递推思想的运用

错位排序

错位排列

有n个信封和n个信件,第i个信件属于第i个信封,我们想知道,有多少种不同的方法,使得没有任何一个信件被装入正确的信封中?答案对998244353取模。
下图中,2号信件装入了2号信封中,因此方案不合法在这里插入图片描述
而下图,就满足“任何一个信件都没有被装入正确的信封中”,因此是合法方案:
在这里插入图片描述
我们开一个数组int f[n];,其中f[i]表示当信封和信件数量为i的时候,总方案数是多少。接下来,我们应该如何寻找递推式呢?

f(1)=0, f(2)=1是初始条件
考虑1号信件,它不能被装入1号信封,不妨假设它被装入了x号信封。这里为了方便,我们假设x = 3。
那么x号信件可以装入哪个信封呢?这里又存在两种情况。

第一种情况:x号信件装入了1号信封:在这里插入图片描述
在这种情况下,我们可以去掉1号和x号,就变成了完全独立的子问题:n-2个信件和信封的错位排列问题。

第二种情况:x号信件没有装入1号信封。这个时候,如果我们去掉1号信件和x号信封,情况就会变成下图:在这里插入图片描述
2号、4号、5号信件不能装入对应的信封中,而x号信件不能装入1号信封中,这其实也是一个大小为n-1的错位排列问题。
因此,当1号信件装入x号信封的时候,总共会有两种情况:

  • x号信件装入1号信封,有f[n-2]种方案
  • x号信件不装入1号信封,有f[n-1]种方案

而x的选择有n-1种(除了1都可以),因此我们得到了递推式f[n] = (n-1)(f[n-1] + f[n-2])。

代码

/*有n个信封和n个信件,第i个信件属于第i个信封,我们想知道,有多少种不同的方法,
使得没有任何一个信件被装入正确的信封中?答案对998244353取模。*/
# include<bits/stdc++.h>
using namespace std;

const int mod = 998244353;
const int maxn = 100000 + 10;
//递推算法f(n) = (n-1) * (f(n-1) + f(n-2))
int main()
{
    int n;
    cin >> n;
    int f1 = 0, f2 = 1;
    int ans = 1;
    for(int i = 3; i <= n; i++)
    {
        ans = (1LL * (i - 1) * (f1 + f2) ) % mod;
        f1 = f2;
        f2 = ans;
    }
    cout << ans << endl;
}

杨辉三角(二维递推)

事实上,我们也会经常遇到不止一维的递推,比如我们接下来要介绍的杨辉三角问题。
在这里插入图片描述
比如,当k=4时,你需要输出如下矩阵:

1 0 0 0 0
1 1 0 0 0
1 2 1 0 0
1 3 3 1 0
1 4 6 4 1
其中第4行第2列的数字为6(注意行和列从0开始标号),表示从4个不同的物品中选2个有6种方法。
假设这4个物品分别叫A、B、C、D,那么这6种方法分别是:

AB
AC
AD
BC
BD
CD

类似的,我们开一个二维数组int f[k][k];,其中f[i][j]表示从i个物品中选j个的方案数,接下来,我们要做的就是寻找递推式。

怎么尝试寻找递推式呢?分类讨论吗?对的!我们继续使用分类讨论来寻找递推式。绝大多数简单的递推问题都可以用分类讨论的方法找到一个合理的递推式。

假设我们现在要求的值是f[i][j],即,从i个物品中选j个的方案数,我们不妨把i个物品从1到i标上号。
现在考虑1号物品,我们尝试对1号物品进行分类讨论。怎么分类呢?无非就是两类:选1号物品,还是不选1号物品。

选1号物品:由于1号物品是一定要选进来的,因此我们还剩i-1个物品,我们要从中选出j-1个物品,方案数是f[i-1][j-1]。
不选1号物品:我们还剩i-1个物品,但是1号一定不选,因此我们还要从剩下的i-1个物品中选出j个物品,方案数是f[i-1][j]。
结束了吗?其实这样就结束了。因为我们已经不重复不遗漏地考虑到了所有可能出现的情况。

所以我们就得到了递推式:f[i][j] = f[i-1][j-1] + f[i-1][j]。

边界条件:
接下来,我们发现样例矩阵的第一列和对角线全都是1,这也是容易推理出的:第一列的所有元素都是f[x][0],从x个物品中选取0个物品,显然只有一种方案:什么都不选;而对角线的所有元素都是f[x][x],从x个物品中选出x个物品,也只有一种方案:全部都选上。

除此之外还有其他的边界条件吗?

没有了。

因为我们的递推式f[i][j] = f[i-1][j-1] + f[i-1][j]告诉我们,如果我们想要计算任何一个数字f[i][j],只需要知道它“上面”的数字f[i-1][j]和“左上方”的数字f[i-1][j-1]即可。观察矩阵,我们发现,对于任何一个数字,我们都可以用已知的初始条件推出,因此我们不需要更多的边界条件了。
在这里插入图片描述
除此之外还有什么需要注意的么?有的!我们还需要特别注意递推的顺序!二维递推不同于一维递推,二维的数据可能存在莫名其妙的依赖关系,因此我们在写二维递推的时候,需要特别注意二维递推的顺序。

比如杨辉三角,我们可以从递推式和上图看出,f[i]这一行的值全部由f[i-1]这一行的值推出,因此我们只需要按照行数从小到大的顺序递推即可。而其他形式的二维递推可能就需要用其他顺序进行循环,比如下面这种递推形式。
在这里插入图片描述
搞定了递推式、初始条件、递推顺序之后,我们的代码就呼之欲出了。

同时,递推出单个元素的复杂度是O(1),整个表格一共有O(n2)个元素,因此该算法的总时间复杂度是O(n2)。

// 杨辉三角递推
# include <bits/stdc++.h>
using namespace std;

int main()
{
    int n;
    cin >> n;
    vector<vector<int>> dp(n + 1, vector<int>(n + 1, 0));
    
    //dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
    //初始条件dp[i][0]=1,dp[i][i]=1,设置初始条件
    for(int i = 0; i <= n; i++)
    {
        dp[i][0] = 1;
        dp[i][i] = 1;
    }
    //递推
    for(int i = 2; i <= n; i++)
    {
        for(int j = 1; j <= i; j++)
        {
            dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
        }
    }
    //输出
    for(int i = 0; i <= n; i++)
    {
        for(int j = 0; j <= n; j++)
        {
            cout << dp[i][j] << " ";
        }
        cout << endl;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/436676.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[STL]vector的使用+模拟实现

[STL]vector的使用模拟实现 文章目录 [STL]vector的使用模拟实现一、vector的使用1.构造函数2.迭代器3.容量操作4.vector的访问5.vector的修改 二、几个细节1.范围for2.扩容机制3.迭代器失效4.构造函数错误调用5.vector的深拷贝与浅拷贝6.vector的框架 三、vector模拟实现vecto…

hitcon_2017_ssrfme、[BJDCTF2020]Easy MD5、[极客大挑战 2019]BuyFlag

hitcon_2017_ssrfme 进入环境给出源码 <?php if (isset($_SERVER[HTTP_X_FORWARDED_FOR])) {$http_x_headers explode(,, $_SERVER[HTTP_X_FORWARDED_FOR]);$_SERVER[REMOTE_ADDR] $http_x_headers[0];}echo $_SERVER["REMOTE_ADDR"];$sandbox "sandbo…

Leetcode225. 用队列实现栈

文章目录 1.题目描述2.原题链接3.思路分析4.代码实现 1.题目描述 请你仅使用两个队列实现一个后入先出&#xff08;LIFO&#xff09;的栈&#xff0c;并支持普通栈的全部四种操作&#xff08;push、top、pop 和 empty&#xff09;。 实现 MyStack 类&#xff1a; void push(int…

树上差分(点差分/边差分)

树上差分一般有两种类型的题目&#xff0c;一种是对边进行差分&#xff0c;另一种就是对点进行差分。 对应的操作也有两种&#xff0c;对边进行差分的对应操作就是给定一对节点(u,v)&#xff0c;让我们把u到v之间路径上的边权都加val&#xff0c;对点进行差分的对应操作就是给…

经验正交分解EOF的Matlab的实现示例

在地学中&#xff0c;PCA和EOF通常用于信号提取&#xff0c;从繁杂的时空数据中分离出地理要素的时空变化特征&#xff0c;是进行地学信号分析的前提。本质上PCA和EOF没有什么不同&#xff0c;只是&#xff1a;EOF为空间特征向量&#xff0c;也称为空间模态&#xff0c;在一定程…

信号完整性分析:关于传输线的三十个问题解答(一)

1.什么是真正的传输线&#xff1f;&#xff08;What is a real transmission line?&#xff09; 答&#xff1a;真正的传输线由任意两条延长一定长度的导体组成。将一根导线标记为信号路径&#xff0c;将另一根导线标记为返回路径。 A real transmission line is composed o…

2023最经典的Python接口自动化测试中的用例编写问题总结

本篇文章分享几个接口自动化用例编写过程遇到的问题总结&#xff0c;希望能对初次探索接口自动化测试的小伙伴们解决问题上提供一小部分思路。 B站讲的最详细的Python接口自动化测试实战教程全集&#xff08;实战最新版&#xff09;_哔哩哔哩_bilibiliB站讲的最详细的Python接…

4月,不要跳槽...

跳槽是每个人都可能面临的选择&#xff0c;但不同的时间点会对跳槽带来不同的影响。对于软件测试人员来说&#xff0c;4月份并不是最适合的跳槽时间。原因如下&#xff1a; 与企业目标和计划相关。一般情况下&#xff0c;公司在1月份会制定本年度的发展目标和计划&#xff0c;而…

力扣sql中等篇练习(五)

力扣sql中等篇练习(五) 1 股票的资本收益 1.1 题目内容 1.1.1 基本题目信息 1.1.2 示例输入输出 1.2 示例sql语句 # 每个用户的所有Sell的price值减去Buy的price值就可以了 SELECT stock_name,SUM(IF(operationBuy,price*-1,price)) capital_gain_loss FROM Stocks GROUP B…

IT知识百科:什么是SSID?

一、什么是SSID SSID&#xff08;Service Set Identifier&#xff09;是无线网络中的一个重要概念&#xff0c;它是一个用于标识无线局域网&#xff08;WLAN&#xff09;的名称。SSID可以看作是无线网络的名称&#xff0c;类似于有线网络中的网络名称或者路由器的名称。在无线…

【JavaScript】5.JavaScript内置对象

JavaScript 内置对象 JavaScript 中的对象分为3种 自定义对象内置对象浏览器对象 前面两种对象是JS 基础 内容&#xff0c;属于 ECMAScript&#xff1b; 第三个浏览器对象属于JS 独有的 内置对象就是指 JS 语言自带的一些对象&#xff0c;这些对象供开发者使用&#xff0c;…

数据通信基础 - 数据通信方式

文章目录 1 概述2 分类2.1 按通信方向分2.2 按同步方式分 3 扩展3.1 网工软考真题 1 概述 分类维度分类解释举例通信方向单工通信信息 只能在一个方向发送&#xff0c;发送方不能接收&#xff0c;接收方不能发送电视、广播半双工通信通信双方可以 交替发送和接收信息&#xff…

分布式锁+AOP实现缓存

分布式锁AOP实现缓存 1、分布式锁AOP实现思想2、不使用AOP的情况2.1 没有使用缓存时代码2.2 使用Redis实现分布式锁的代码2.3 使用Redisson实现分布式锁2.4 测试缓存命中2.5 存在问题 3、分布式锁AOP实现3.1 定义注解3.2 定义一个切面类加上注解3.3 使用注解完成缓存 1、分布式…

函数的缺省参数,函数重载与底层函数名修饰解释,引用的初步介绍

TIPS 使用C输入输出更方便&#xff0c;不需要像printf/scanf输入输出时那样&#xff0c;需要手动控制格式。C的输入输出可以自动识别变量类型。在日常练习中&#xff0c;建议直接using namespace std即可&#xff0c;这样就很方便。using namespace std展开&#xff0c;标准库…

ReetrantLock源码剖析_03公平锁、非公平锁

一直努力就会有offer&#xff0c;一直努力就会有offer&#xff0c;一直努力就会有offer&#xff01; 文章目录 ReetrantLock公平锁代码解析ReetrantLock公平锁执行流程ReetrantLock非公平锁代码解析ReetrantLock非公平锁执行流程公平锁与非公平锁的比较 ReetrantLock公平锁代码…

前端部署发布项目后,如何通知用户刷新页面、清除缓存

以下只是一些思路&#xff0c;有更好的实现方式可以留言一起交流学习 方式一&#xff1a;纯前端 在每次发布前端时&#xff0c;使用webpack构建命令生成一个json文件&#xff0c;json中写个随机生成的一个字符串&#xff08;比如时间戳&#xff09;&#xff0c;每次打包程序都…

【Python入门第五十天】Python丨NumPy 数组搜索

搜索数组 可以在数组中搜索&#xff08;检索&#xff09;某个值&#xff0c;然后返回获得匹配的索引。 要搜索数组&#xff0c;请使用 where() 方法。 实例 查找值为 4 的索引&#xff1a; import numpy as nparr np.array([1, 2, 3, 4, 5, 4, 4])x np.where(arr 4)pri…

node可以用nvm快速切换版本,golang如何快速切换版本?用gvm就行。

使用 gvm 可以带来以下好处&#xff1a; 快速切换 Golang 版本&#xff0c;方便进行版本测试和开发&#xff1b;可以在多个项目中同时使用不同版本的 Golang 包和工具&#xff0c;避免冲突&#xff1b;可以通过 gvm 管理不同版本的 Golang&#xff0c;方便安装、卸载和更新&am…

STL--vector

一、vector介绍 vector是表示大小可以更改的数组的序列容器 就像数组一样&#xff0c;vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问&#xff0c;和数组一样高效。但是又不像数组&#xff0c;它的大小是可以动态改变的&#xff0c;而…

移动端屏幕适配

文章目录 移动端屏幕适配移动端屏幕适配和响应式布局区别基本知识简单屏幕适配 移动端屏幕适配 移动端屏幕适配和响应式布局区别 移动端适配响应式布局终端移动端PC端和移动端常用单位宽高&#xff1a;rem 或 %字体&#xff1a;px宽&#xff1a;%高、字体&#xff1a;px宽高宽…