2.3.1 记忆化搜索与动态规划(《挑战程序设计竞赛》)

本文介绍了记忆化搜索的概念及应用,并通过背包问题和最长公共子序列问题详细阐述了其如何减少重复计算的过程。进一步地,文章从记忆化搜索出发,讲解了动态规划的基本思想及其在解决实际问题中的应用。

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

PS:贪心终于看完了……


记忆化搜索

意思是:在计算过程中,一边计算,一边记录局部的结果。
应用于对某些状态有重复计算的问题。


例题一:背包问题
这里写图片描述
最直接的思路是:对于每个物品,决定放和不放,所以复杂度是O(2^n)
代码如下:

#include<iostream>
#include<stdio.h>
#include<cstring>
using namespace std;
int n,w[105],v[105],a[105][105];
int bag(int i,int j)
{   //i是选择物品的编号,j是背包的空间
    int res;
    if(i==n) res=0; //没有剩余物品,价值为0
    else if(w[i]>j) res=bag(i+1,j); //放不下,就不放
         else res=max( bag(i+1,j),bag(i+1,j-w[i])+v[i] );
         //放得下的话,看看放和不放的价值哪个大,因为放了空间会减小,所以是j-w[i]
    return res;
}
int main()
{
    int ans,sum,i;
    //freopen("a.txt","r",stdin);
    scanf("%d%d",&n,&sum);
    for(i=0;i<n;i++) scanf("%d%d",&w[i],&v[i]);
    memset(a,-1,sizeof(a));
    ans=bag(0,sum);
    printf("%d\n",ans);
}

这种思路虽然直观,但是会有重复计算的地方,如下图
这里写图片描述
这里(3,2)的情况重复计算了。

所以:我们会想到,如果能把结果存起来,遇到将要重复计算的时候,只要提取之前记录的结果就可以了,这样时间会大大减少。
改进代码如下:

#include<iostream>
#include<stdio.h>
#include<cstring>
using namespace std;
int n,w[105],v[105],a[105][105];
int bag(int k,int s)
{   //用a[k][s]来记录曾经计算过的状态
    if(a[k][s]>=0) return a[k][s]; //曾经计算过,就可以用,不用重复计算
    if(k==n) a[k][s]=0; 
    else if(w[k]>s) a[k][s]=bag(k+1,s);
         else a[k][s]=max( bag(k+1,s),bag(k+1,s-w[k])+v[k] );
    return a[k][s];
}
int main()
{
    int ans,sum,i;
    //freopen("a.txt","r",stdin);
    scanf("%d%d",&n,&sum);
    for(i=0;i<n;i++) scanf("%d%d",&w[i],&v[i]);
    memset(a,-1,sizeof(a)); //因为可能为0,所以设置为-1,
    ans=bag(0,sum);
    printf("%d\n",ans);
}


动态规划(DP)

根据上面的代码,我们可以得出如下递推式
这里写图片描述
对应表格如下:
这里写图片描述
当n=4的时候,因为d[n][j]表示第4号即第五个物品,并不存在,所以设定价值为0

dp[3][ j ]=max( dp[4][ j ],dp[4][ j - w[3] ] + v[3] ) 表示对于第3号物品,在重量为 j 的情况之下,对比第3号物品拿和不拿,赋最大价值给dp[3][ j ]
① 不拿的值是 dp[4][ j ],即继承上一个重量为 j 的状态
② 拿的值是dp[4][ j - w[3] ] + v[3],即选择上一个重量为 j - w[3] 的状态再加上自己的价值,因为要空出 w[3] 的空间来装下拿走的第3号物品,且拿走之后要保持背包重量为 j

总结一下书上的动态规划含义:不用写递归函数,直接利用递推式将各项的值计算出来,简单地使用循环,一步步按顺序求出问题的解。

在解决问题时可以从记忆化搜索出发,推导出递推式,熟练之后也可以直接得出递推式

(PS:我还是觉得递归比较直观简单)


例题二:最长公共子序列问题(LCS)
这里写图片描述
思路是:
dp[ i ][ j ] 用来存放s1~s i和t1~t j对应的LCS长度
(要注意的是,这里s1指的是s[0],即a,s3指的是s[0]s[1]s[2],即abc)
假设知道了dp[ i ][ j ],那么dp[ i+1 ][ j+1 ] 的可能是
① 当 s[ i ] == t[ j ],就是dp[ i ][ j ] + 1
② 当 s[ i ] != t[ j ],就是【s1~s i 和 t1~t (j+1) 的公共子列长度 】 与 【s1~s (i+1) 和 t1~t j 的公共子列长度】之中的较大值
例如:已知dp[1][1],要求dp[2][2],因为s[1] != t[1],所以dp[2][2]] 等于【s1和t1t2的公共子列长度】与【s1s2和t1的公共子列长度】两者之中的最大值
代码为:dp[2][2]=max( dp[1][2],dp[2][1] ) ;

所以有如下递推关系:
这里写图片描述
对应的表格:(书上左上角的 j \ i 应该是写错了,这里修改过来)
这里写图片描述
j=0的那一列 与 i=0的那一行设为0
想要知道dp[a+1][b+1],就得知道它的基础dp[a][b] 或者是 dp[a+1][b]与dp[a][b+1]
而i=a,j=b的时候,这些基础已经知道了确切的值,所以能推出 dp[ i+1 ][ j+1 ]
不太清楚的话,可以按照代码把表格填一遍

代码如下:

#include<iostream>
#include<stdio.h>
#include<cstring>
using namespace std;
int dp[1005][1005]; //太大要定义在main外边
int main()
{
    int n,m,i,j;
    char s[1005],t[1005];
    scanf("%d%d",&n,&m);
    memset(dp,0,sizeof(dp));
    scanf("%s",s);
    scanf("%s",t);
    for(i=0;i<n;i++)
        for(j=0;j<m;j++)
            if(s[i]==t[j]) dp[i+1][j+1]=dp[i][j]+1;
            else dp[i+1][j+1]=max( dp[i][j+1],dp[i+1][j] );
    printf("%d\n",dp[n][m]);
}
1. 用户身体信息管理模块 用户信息管理: 注册登录:支持手机号 / 邮箱注册,密码加密存储,提供第三方快捷登录(模拟) 个人资料:记录基本信息(姓名、年龄、性别、身高、体重、职业) 健康目标:用户设置目标(如 “减重 5kg”“增肌”“维持健康”)及期望周期 身体状态跟踪: 体重记录:定期录入体重数据,生成体重变化曲线(折线图) 身体指标:记录 BMI(自动计算)、体脂率(可选)、基础代谢率(根据身高体重估算) 健康状况:用户可填写特殊情况(如糖尿病、过敏食物、素食偏好),系统据此调整推荐 2. 膳食记录食物数据库模块 食物数据库: 基础信息:包含常见食物(如米饭、鸡蛋、牛肉)的名称、类别(主食 / 肉类 / 蔬菜等)、每份重量 营养成分:记录每 100g 食物的热量(kcal)、蛋白质、脂肪、碳水化合物、维生素、矿物质含量 数据库维护:管理员可添加新食物、更新营养数据,支持按名称 / 类别检索 膳食记录功能: 快速记录:用户选择食物、输入食用量(克 / 份),系统自动计算摄入的营养成分 餐次分类:按早餐 / 午餐 / 晚餐 / 加餐分类记录,支持上传餐食照片(可选) 批量操作:提供常见套餐模板(如 “三明治 + 牛奶”),一键添加到记录 历史记录:按日期查看过往膳食记录,支持编辑 / 删除错误记录 3. 营养分析模块 每日营养摄入分析: 核心指标计算:统计当日摄入的总热量、蛋白质 / 脂肪 / 碳水化合物占比(按每日推荐量对比) 微量营养素分析:检查维生素(如维生素 C、钙、铁)的摄入是否达标 平衡评估:生成 “营养平衡度” 评分(0-100 分),指出摄入过剩或不足的营养素 趋势分析: 周 / 月营养趋势:用折线图展示近 7 天 / 30 天的热量、三大营养素摄入变化 对比分析:将实际摄入推荐量对比(如 “蛋白质摄入仅达到推荐量的 70%”) 目标达成率:针对健
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值