第五周集训总结

这篇博客总结了第五周关于动态规划的学习,包括递推算法、基础动态规划思想、最长递增子序列的解法,以及背包问题的探讨。通过训练例题,如数塔问题、昆虫繁殖等,深入解析动态规划的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

动态规划

1.递推算法

例题 位数问题

【问题描述】
在所有的N位数中,有多少个数中有偶数个数字3?由于结果可能很大,你只需要输出这个答案对12345取余的值。

【输入格式】
读入一个数N

【输出格式】
输出有多少个数中有偶数个数字3。

【输入样例】
2
【输出样例】
73
【数据规模】
1<=N<=1000

【样例说明】
在所有的2位数字,包含0个3的数有72个,包含2个3的数有1个,共73个

理解

考虑这种题目,一般来说都是从第i-1位推导第i位,且当前位是取偶数还是取奇数的。
可以用f[i][0]表示前i位有偶数个3的方案数
f[i][1]表示前i位有奇数个3的方案数
则状态转移方程可以表示为:
f[i][0]=f[i-1][0]*9+f[i-1][1];
f[i][1]=f[i-1][0]+f[i-1][1]*9;
边界条件:f[1][1]=1; f[1][0]=8;

例题代码

#include <iostream>
using namespace std;
const int M=12345;//取模
const int N=1000+5; //最多1000位数
int main()
{
   
//f[i][0]表示前i位有偶数个3的方案数,f[i][1]表示前i位有奇数个3的方案数
int f[N][2],n;
f[1][0]=8;f[1][1]=1;//1位数的情况
cin>>n;
for(int i=2;i<=n;i++){
   
f[i][0]=(f[i-1][0]*9+f[i-1][1])%M;
f[i][1]=(f[i-1][1]*9+f[i-1][0])%M;
}
cout<<f[n][0]<<endl;
return 0;
}

2.基础dp

算法思想

如果各个子问题不是独立的,如果能够保存已经解决的子问题的答案,而在需要的时候再找出已求得的答案,这样就可以避免大量的重复计算。
基本思路是,用一个表记录所有已解决的子问题的答案,不管该问题以后是否被用到,只要它被计算过,就将其结果填入表中。

求解过程

在这里插入图片描述

基本步骤

动态规划算法通常用于求解具有某种最优性质的问题。在这类问题中,可能会有许多可行解。每一个解都对应于一个值,我们希望找到具有最优值(最大值或最小值)的那个解。

动态规划法设计算法一般分成三个阶段:
(1)分段:将原问题分解为若干个相互重叠的子问题;
(2)分析:分析问题是否满足最优性原理,找出动态规划函数的递推式;
(3)求解:利用递推式自底向上计算,实现动态规划过程。
动态规划法利用问题的最优性原理,以自底向上的方式从子问题的最优解逐步构造出整个问题的最优解。

滚动数组

•处理dp[][]状态数组的时候,有个小技巧:把它变成一维的dp[],以节省空间。
•观察二维表dp[][],可以发现,每一行是从上面一行算出来的,只跟上面一行有关系,跟更前面的行没有关系。
•那么用新的一行覆盖原来的一行就好了。

3.求最长递增子序列

1、暴力法:枚举所有的子序列,判断是不是递增的,如果是递增的求最大值。
2、采用LCS的方法:
1)原序列A排序得到B
2) 求A和B的LCS
3、直接DP
4、借助二分查找,优化为O(nlogn)

dp解法

先确定动态规划的状态,这个问题可以用序列某一项作为结尾来作为一个状态。
用dp[i]表示一定以第i 项为结尾的最长上升子序列。
用a[i] 表示第i 项的值
如果有j < i且a[j] < a[i],那么把第i 项接在第j 项后面构成的子序列长度为:dp[i] = dp[j] + 1。
•要使dp[i] 为以i 结尾的最长上升子序列,需要枚举所有满足条件的j。所以转移方程是:
在这里插入图片描述

3.递推与记忆化搜索

•前面DP的状态转移,都是用递推的方法。
•有另一种方法,逻辑上的理解更加直接,这就是用“递归+记忆化搜索”来实现DP。

记忆化搜索思想

用递归实现DP时,在递归程序中记录计算过的状态,并在后续的计算中跳过已经算过的重复的状态,从而大大减少递归的计算次数,这就是“记忆化搜索”的思路。

•递归时,有大量重复计算,其实能避免。
•观察第3层的中间数“1”,从第2层的“3”往下走会经过“1”,计算一次从“1”出发的递归;从第2层的“8”往下走会也经过“1”,又重新计算了从“1”出发的递归。所以,只要避免这些重复计算,就能优化。
7(30)
3(23) 8(21)
8(20) 1(13) 0(10)
2(7) 7(12) 4(10) 4(10)
4(4) 5(5) 2(2) 6(6) 5(5)

4.背包问题

1.0/1背包
2.完全背包
3.多重背包
4.混合背包
5.分组背包
6.依赖背包
我认为后面的背包问题都是由0/1背包问题衍生出来,所以把0/1背包问题思想理解透彻才是最根本的

0/1背包

一、定义状态:
a[i][j]:表示容量为j的背包选择前i件物品的最大价值和
二、状态转移方程
1、w[i] > j ,第i件物品太重了,容量为j的背包装不下
a[i][j] = a[i -1][j]
2、w[i] <= j
a[i][j] = max(a[i -1][j],a[i -1][j -w[i]] + v[i]);

理解

在0/1背包问题中,物品i或者被装入背包,或者不被
装入背包,设xi
表示物品i装入背包的情况,
则当xi=0时,表示物品i没有被装入背包,
xi=1时,表示物品i被装入背包。
根据问题的要求,有如下约束条件和目标函数:
在这里插入图片描述
按这样的规律一行行填表,直到结束。现在回头考虑,装了哪些物品。
看最后一列,15>14,说明装了物品5,否则价值不会变化。
在这里插入图片描述

#include<bits/stdc++.h>
intn,c;//n件物品,背包容量为c
intw[N],v[N];//重量是w,价值是V
cin>> n >> c;//读入物品数量n和背部的容量c
for(inti= 1;i <= n;i++)//读入每件物品的重量和价值
cin>> w[i] >> v[i];
//用dp方程建表,行表示物品[1,n],列表示背包容量[1,c]
//dp[i]表示容量为i的背包选择物品的最大价值和
intdp[N] = {
   0};//初始化表为0
//顺序遍历n件物品
for(inti= 1;i <= n;i++){
   
for(intj = c; j >= w[i]; j--){
   //背包容量[1,c],从右到左填表
//不选当前物品和选当前物品(背包容量变为j-w[i])两种方案求最大值
dp[j] = max(dp[j],dp[j -w[i]] + v[i]);
}
}

训练例题

数塔问题

题目描述

设有一个三角形的数塔,顶点为根结点,每个结点有一个整数值。从顶点出发,可以向左走或向右走,如图所示
在这里插入图片描述
若要求从根结点开始,请找出一条路径,使路径之和最大,只要输出路径的和。

输入

第一行为n(n<10),表示数塔的层数

从第2行至n+1行,每行有若干个数据,表示数塔中的数值

输出

输出路径和最大的路径值。

样例输入

5
13
11 8
12 7 26
6 14 15 8
12 7 13 24 11

样例输出

86

#include<bits/stdc++.h>
using namespace std;


int main(
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值