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

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]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值