动态规划的设计思想与实例(最大子段和、最长公共子序列、0-1背包、编辑距离)

动态规划算法与分治法类似,其基本思想是将总问题分解成若干个子问题,先求解子问题,再结合这些子问题的解得到原问题的解。与分治法不同的是,动态规划求解的问题经分解得到的子问题往往不是相互独立的。

基本思想:

将总问题分解成多个子问题(子问题也可以继续分解,直到无法分解),计算子问题,用一个表保存已解决的子问题的答案,算完子问题后回到总问题时从表中寻找已求得的答案,根据要求挑选最优解,加上总问题的里的具体变化再存入表中

设计步骤:

1.找出最优解的性质,并刻画其结构特征

  根据具体问题找出其结构的特点,按该特点分解问题,使问题可以从其子问题的答案中寻找解

2.递归地定义最优值

  写递归表达式,即子问题的答案中最合适的应该满足哪些条件 总问题的解 = 最优解(子问题1,子问题2,子问题3……)+ 一定变化

3.以自底向上的方式计算最优值

  存储子问题解的表一般为二维数组dp,按从左到右从上到下的顺序从下标(0,0)到(m,n)存储子问题的答案,在计算某一个子问题(r,c)时,根据之前计算好的解来得出答案,例如dp[l][r]=max(dp[r-1][c],dp[r][c-1])+1,表示问题(r,c)的解依据是子问题(r-1,c)和(r,c-1)的解

4.根据计算最优值时得到的信息,构造最优解

  看问题的解的具体要求,一般直接为dp[m][n]

实例

最大子段和

给定n个整数(可能为负整数)组成的序列a1,a2……an,求该序列的子段和的最大值(al+al+1+al+2+……+ar-1+ar),当子段和小于0时定义其为0。

分析问题,可以得到特征:

1.子段是连续的

2.当子段中所有负整数与正整数相加的结果小于0时该子段和取0,即子段和大于等于0

3.子段和最大的子段一定会以某个元素ai为结尾

(先简称以ai-1、ai为结尾的子段和最大的两个子段为子段ai-1和子段ai

结合1和3可以得到关系:子段ai一定包括元素ai(因为以它为结尾),该子段的ai元素的上一位一定是ai(因为子段连续),或者是空(即整个子段只有ai),那么以ai为结尾的子段的最大子段和为:max(以ai-1为结尾的子段的最大字段和+ai,0)

即递归表达式:dp[i]=max(dp[i-1]+a[i],0)(其中dp[i]指子段ai的子段和,a[i]指ai

(其实不需要a数组,一个dp数组就够了)

#include<iostream>
using namespace std;
int main()
{
    int dp[100000],m,n,i;
    while(cin>>n)
    {
        for(i=0;i<n;i++)
            cin>>dp[i];
        m=dp[0]=max(dp[0],0);
        for(i=1;i<n;i++)
        {
            dp[i]=max(dp[i-1]+dp[i],0);
            m=max(m,dp[i]);
        }
        cout<<m<<‘\n‘;
    }
    return 0;
}

最长公共子序列

给定序列x={x1,x2……xm}和y={y1,y2,……yn},求x和y的最长公共子序列z

已知:子序列z的元素在序列x、y里不一定是连续的,但一定是按序列x、y里的下标的大小从小到大排好的

设dp[i][j]为x、y的从第一个元素开始、长度分别为i、j的子段的最长公共子序列的长度,则对于x[i]、y[j]的关系:

当i=0或jj=0时,x、y其中一边或两边长度为0,公共子序列长度也为0,则:

  dp[i][j]=0

当x[i]=y[j]时,x[i]、y[j]直接算为公共子序列里的元素,当前最长公共子序列为上一次的最长公共子序列(dp[i-1][j-1]的时候)加上x[i]、y[j],即:

  dp[i][j]=dp[i-1][j-1]+1

当x[i]!=y[j]时,x[i]、y[j]不能直接算作公共子序列里的元素,但x[i]、y[i]可能会因为和之前的元素相等而加入公共子序列(x[i]!=y[j],但可能x[i]=y[j-1],y[j]同样),当前最长公共子序列为除去x[i]或除去y[j]的两种情况下最长公共子序列较大的的一方:

  dp[i][j]=max(dp[i-1][j],dp[i][j-1])

输出最长公共子序列时可根据dp[i][j]与dp[i-1][j]、dp[i][j-1]、dp[i-1][j-1]的大小关系判断dp[i][j]来源于哪个(这里由于while语句里i、j都自减了,所以下面的if语句里是反过来自增来确保i、j不必要的变化)

#include<iostream>
using namespace std;
int dp[1001][1001]={0},x[1001],y[1001],z[1001];
int main()
{
    int m,n,i,j,k;
    while(cin>>m>>n)
    {
        for(i=1;i<=m;i++)
            cin>>x[i];
        for(i=1;i<=n;i++)
            cin>>y[i];
        for(i=1;i<=m;i++)
            for(j=1;j<=n;j++)
                if(x[i]!=y[j])dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
                else dp[i][j]=dp[i-1][j-1]+1;
        k=0;
        while(dp[--i][--j])
            if(dp[i][j]==dp[i-1][j])j++;
            else if(dp[i][j]==dp[i][j-1])i++;
            else if(dp[i][j]>dp[i-1][j-1])z[++k]=x[i];
        cout<<dp[m][n]<<‘\n‘;
        while(k)cout<<z[k--]<<‘ ‘;
        cout<<‘\n‘;
    }
    return 0;
}

0-1背包

给定n种物品和一背包。物品i的重量是wi,其价值为vi,背包的容量为c,不能多次装入物品i,也不能装入部分物品i。应如何选择装入背包的物品,使得装入背包中物品的总价值最大?

设dp[i][j](1<=i<=n,1<=j<=c),i表示可选择的物品有1、2……i,j表示当前背包容量,则对于当前dp[i][j]:

当i=0或j=0时初始化:

  dp[i][j]=0

当j<w[i]时,物品放不下,直接舍弃物品i,取上次的结果,即可选物品为1、2……i-1,背包容量同样为j的时候:

  dp[i][j]=dp[i-1][j]

当j>=w[i]时,尝试放入物品,在上次的结果,以及这次放入后的结果中,取较大值:

  dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i])

这里的的dp[i-1][j-w[i]]+v[i]中:

1.[i-1]是因为物品i只能放一次,放入物品后i其他放入物品的范围只能是物品1、2……i-1

2.[j-w[i]]是指物品i放入时至少要留出w[i]的空间,剩余空间的存放情况相当于容量为j-w[i]的背包在物品范围1、2……i-1的存放情况

#include<iostream>
using namespace std;
int dp[1001][1001]={0},w[1001],v[1001];
int main()
{
    int n,c,i,j;
    while(cin>>n>>c)
    {
        for(i=1;i<=n;i++)
            cin>>w[i]>>v[i];
        for(i=1;i<=n;i++)
            for(j=1;j<=c;j++)
                if(j<w[i])dp[i][j]=dp[i-1][j];
                else dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
        cout<<dp[n][c]<<‘\n‘;
    }
    return 0;
}

编辑距离

一个字符串有三种操作方式:

1.替换:将字符串的任意一个字符改为另一种字符

2.删除:删除字符串中的任意一个字符

3.插入:添加一个字符在字符串的任意位置

给定两个字符串a、b,求最少操作的数量使两个字符串相等(即编辑距离)

设dp[i][j]表示字符串a、b的子串a[1~i]、b[1~j]的编辑距离

当i=0或j=0时,代表至少有一个空串,显然:

  dp[i][j]=max(i,j)

当a[i]=b[j]时,a[i]、b[j]直接排除不考虑,相当于a[1~i]、b[1~j]的编辑距离:

  dp[i][j]=dp[i-1][j-1]

当a[i]!=b[j]时,有多种情况:

  进行替换操作时,改变a[i]或b[j]使其与另一个相等,编辑距离加1,再加上a[1~i-1]、b[1~j-1]的编辑距离即:dp[i][j]=dp[i-1][j-1]+1

  进行删除操作时,删除a[i]或b[j],编辑距离加1再加上a[1~i-1]、b[1~j](或a[1~i]、b[1~j-1])的编辑距离即:dp[i][j]=dp[i-1][j]+1(或dp[i][j]=dp[i][j-1]+1)

  进行插入操作时,插入a[i+1]使a[i+1]=b[j](b[j+1]同样),编辑距离加1加a[1~i]、b[1~j-1](或a[1~i]、b[1~j-1])的编辑距离即:dp[i][j]=dp[i-1][j]+1(或dp[i][j]=dp[i][j-1]+1)

  可以看出删除和插入操作表达式是一样的

由于dp[i][j]和字符串a,b开始下标不同,所以if语句里是i-1、j-1

#include<iostream>
using namespace std;
int dp[2001][2001];
int main()
{
    int n,m,i,j;
    string a,b;
    while(cin>>a>>b)
    {
        n=a.length();
        m=b.length();
        for(i=0;i<=n;i++)
            dp[i][0]=i;
        for(j=0;j<=m;j++)
            dp[0][j]=j;
        for(i=1;i<=n;i++)
            for(j=1;j<=m;j++)
                if(a[i-1]==b[j-1])dp[i][j]=dp[i-1][j-1];
                else dp[i][j]=min(min(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1])+1;
        cout<<dp[n][m]<<‘\n‘;
    }
    return 0;
}

原文地址:https://www.cnblogs.com/MTstory/p/12020845.html

时间: 2024-10-11 16:30:19

动态规划的设计思想与实例(最大子段和、最长公共子序列、0-1背包、编辑距离)的相关文章

【算法导论之七】动态规划求解最长公共子序列

一.动态规划的概念 动态规划(Dynamic Programming)是通过组合子问题的解而解决整个问题的.分治算法是指将问题划分成一些独立的子问题,递归地求解各子问题,然后合并子问题的解而得到原始问题的解,与此不同,动态规划适用于子问题不是独立的情况,也就是各个子问题包含公共的子问题.在这种情况下,采用分治法会做许多不必要的工作,即重复地求解公共地子问题.动态规划算法对每个子问题只求解一次,将其结果保存在一张表中,从而避免每次遇到各个子问题时重新计算答案. 动态规划通常应用于最优化问题.此类问

动态规划求解最长公共子序列

动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解.与分治法不同的是,适合于用动态规划求解的问题,经分解得到的子问题往往不是互相独立的.若用分治法来解决这类问题,则分解得到的子问题数目太多,以至于最后解决原问题需要耗费指数时间.然而,不同子问题的数目常常只有多项式量级.在用分治法求解时,有些子问题被重复计算了许多次.如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,从而得到多项式

算法导论_动态规划_最长公共子序列

一.动态规划的概念 动态规划(Dynamic Programming)是通过组合子问题的解而解决整个问题的.分治是指将问题划分成一些独立的子问题,递归地求解各子问题,然后合并子问题的解而得到原始问题的解,与此不同,动态规划适用于子问题不是独立的情况,也就是各个子问题包含公共的子问题.在这种情况下,采用分治法会做许多不必要的工作,即重复地求解公共地子问题.动态规划算法对每个子问题只求解一次,将其结果保存在一张表中,从而避免每次遇到各个子问题时重新计算答案. 动态规划通常应用于最优化问题.此类问题可

最长公共子序列问题---动态规划

最长递增子序列问题是一个很基本.较常见的小问题,但这个问题的求解方法却并不那么显而易见,需要较深入的思考和较好的算法素养才能得出良好的算法.由于这个问题能运用学过的基本的算法分析和设计的方法与思想,能够锻炼设计较复杂算法的思维,我对这个问题进行了较深入的分析思考,得出了几种复杂度不同算法,并给出了分析和证明. 一,    最长递增子序列问题的描述 设L=<a1,a2,…,an>是n个不同的实数的序列,L的递增子序列是这样一个子序列Lin=<aK1,ak2,…,akm>,其中k1&l

算法导论之动态规划(最长公共子序列和最优二叉查找树)

动态规划师通过组合子问题的解而解决整个问题,将问题划分成子问题,递归地求解各子问题,然后合并子问题的解而得到原问题的解.和分治算法思想一致,不同的是分治算法适合独立的子问题,而对于非独立的子问题,即各子问题中包含公共的子子问题,若采用分治法会重复求解,动态规划将子问题结果保存在一张表中,避免重复子问题重复求解. 动态规划在多值中选择一个最优解,其算法设计一般分为4个步骤:描述最优解的结构:递归定义最优解的值:按自底向上的方式计算最优解的值:由计算出的结果构造一个最优解. 1)装配线调度 求解最快

动态规划 - 最长公共子序列(LCS)

最长公共子序列也是动态规划中的一个经典问题. 有两个字符串 S1 和 S2,求一个最长公共子串,即求字符串 S3,它同时为 S1 和 S2 的子串,且要求它的长度最长,并确定这个长度.这个问题被我们称为 最长公共子序列问题. 与求最长递增子序列一样,我们首先将原问题分割成一些子问题,我们用 dp[i][j]表示 S1 中前 i 个字符与 S2 中前 j 个字符分别组成的两个前缀字符串的最 长公共子串长度. 显然的,当 i. j 较小时我们可以直接得出答案,如 dp[0][j]必 等于 0.那么,

nyist oj 36 最长公共子序列 (动态规划基础题)

最长公共子序列 时间限制:3000 ms  |  内存限制:65535 KB 难度:3 描述 咱们就不拐弯抹角了,如题,需要你做的就是写一个程序,得出最长公共子序列. tip:最长公共子序列也称作最长公共子串(不要求连续),英文缩写为LCS(Longest Common Subsequence).其定义是,一个序列 S ,如果分别是两个或多个已知序列的子序列,且是所有符合此条件序列中最长的,则 S 称为已知序列的最长公共子序列. 输入 第一行给出一个整数N(0<N<100)表示待测数据组数 接

动态规划解决最长公共子序列问题(转)

原文链接 动态规划法 经常会遇到复杂问题不能简单地分解成几个子问题,而会分解出一系列的子问题.简单地采用把大问题分解成子问题,并综合子问题的解导出大问题的解的方法,问题求解耗时会按问题规模呈幂级数增加. 解决思想: 为了节约重复求相同子问题的时间,引入一个数组,不管它们是否对最终解有用,把所有子问题的解存于该数组中,这就是动态规划法所采用的基本方法. [问题] 求两字符序列的最长公共字符子序列 问题描述:字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后

算法系列笔记6(动态规划—最长公共子序列/串lcs)

子序列要求元素顺序一致就可以了,而字串必须是连续的.如ABCBDAB与BDCABA两个字符串,最长公共子序列有BCBA.BDAB和BCAB, 而最长公共字串只有AB和BD<连续>.当然这里的求解只求一个,但通常是这样直接说求最长公共子串,子序列,准确的应该是之一. 最长公共子序列 法一:穷举法 检查字符串x所有字序列,共有2^m个,检查它是否在y字符串中出现,每个需要O(n),时间复杂度为指数级的. 法二:动态规划(DP) 将两个字符串x[1-m]和y[1-n]放在x轴和y轴方向上便得到一个二