ACM第三专题—动态规划总结

一.概述

  动态规划的基本思想:若要解一个给定问题,我们需要解其不同部分(即子问题),再合并子问题的解以得出原问题的解。 通常许多子问题非常相似,为此动态规划法试图仅仅解决每个子问题一次,从而减少计算量: 一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表。 这种做法在重复子问题的数目关于输入的规模呈指数增长时特别有用。

三大重要性质:

最优子结构性质:如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。最优子结构性质为动态规划算法解决问题提供了重要线索。

子问题重叠性质:子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格中简单地查看一下结果,从而获得较高的效率。

无后效性将各阶段按照一定的次序排列好之后,对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来的决策,而只能通过当前的这个状态。换句话说,每个状态都是过去历史的一个完整总结。这就是无后向性,又称为无后效性。

二.分类解析

1.最长递增子序列.

例题:

题意:给出序列a [1], a [2], a [3] ...... a [n],计算子序列的最大总和。

思路:最大子序列是要找出由数组成的一维数组中和最大的连续子序列。方法是:只要前i项和还没有小于0子序列就一直往后扩展,否则丢弃之前的子序列开始新的子序列,同时记录各个子序列的和,最后取他们中的最大值。

代码:

#include<stdio.h>

#include<iostream>

using namespace std;
int main()
{
    int i,ca=1,t,s,e,n,x,now,before,max;
    scanf("%d",&t);
    while(t--)
    {
       scanf("%d",&n);
       for(i=1;i<=n;i++)
       {
        scanf("%d",&now);
         if(i==1)
         {
            max=before=now;
            x=s=e=1;
         }
         else {
             if(now>now+before)
             {
                before=now;
                x=i;
             }      
             else before+=now;
              }
         if(before>max)
           max=before,s=x,e=i;
       }
       printf("Case %d:\n%d %d %d\n",ca++,max,s,e);
       if(t)printf("\n");
    }
    return 0;
}

2.最长公共子序列.

例题:

题意:求两个字符串的最长公共子序列。

思路:动态的方程在第一个元素的相等的时,dp[0][0] = dp[-1][-1] + 1, 天哪,这肯定就会出错了。在处理时可以选择字符的读取从第一个位置开始,或者把 i 号字符的状态存储到i+1号位置去,这样就从1号开始处理了,判定是就是 s1[i-1] == s1[j-1] ?

代码:

#include<iostream>

#include <cstring>

#include <cstdlib>

#include <cstdio.h>

#define Max( a, b ) (a) > (b) ? (a) : (b)

using namespace std;
 
char s1[1005], s2[1005];
 
int dp[1005][1005];


 int main()
{
     int len1, len2;
     while( scanf( "%s %s", s1+1, s2+1 ) != EOF )
    {
         memset( dp, 0, sizeof(dp) );
        len1 = strlen( s1+1 ), len2 = strlen( s2+1 );
        for( int i = 1; i <= len1; ++i )
         {
             for( int j = 1; j <= len2; ++j )
             {
                 if( s1[i] == s2[j] )
                 {
                    dp[i][j] = dp[i-1][j-1] + 1;
                }
                 else
                 {
                    dp[i][j] = Max ( dp[i-1][j], dp[i][j-1] );
                }
            }
         }
         printf( "%d\n", dp[len1][len2] );
     }
     return 0;
 }

3.背包问题.

<1>01背包.

例题:

题意:一个人收集骨头。给出他的背包容量和可选的骨头的体积和价值,输出他的背包能装下的骨头的最大价值。

思路:01背包问题,DP公式都类似:F[i;v] = maxfF[i-1;v];F[i-1;v-Ci] + Wi,由这个公式做变形就可以。下面再来分析一下这个公式:

每种骨头仅有一件,可以选择放或不放。用子问题定义状态:即F[i;v] 表示前i 件物品恰放入一个容量为v的背包可以获得的最大价值。“将前i 个骨头放入容量为v的背包中”这个子问题,若只考虑第i 个骨头的策略(放或不放),那么就可以转化为一个只和前i-1个骨头相关的问题。如果不放第i 个骨头,那么问题就转化为“前i-1个骨头放入容量为v的背包中”,价值为F[i-1; v];如果放第i 个骨头,那么问题就转化为“前i-1个骨头放入剩下的容量为v-Ci 的背包中”,此时能获得的最大价值就是F[i-1;v-Ci] 再加上通过放入第i 个骨头获得的价值Wi。代码;

#include<iostream>

#include<stdio.h>  

#include<string>  
#define M 1009
using namespace std; 
typedef struct pack  
{  
    int cost;  
    int val;  
}PACK;  
int f[M][M];  
int main()
{  
    int cas,n,v,i,j;  
  
    PACK a[M];  
    scanf("%d",&cas);  
    while(cas--)  
    {  
        scanf("%d%d",&n,&v);  
        memset(f,0,sizeof(f));  
        for(i=1;i<=n;i++)  
            scanf("%d",&a[i].val);
      for(i=1;i<=n;i++)  
            scanf("%d",&a[i].cost);  
        for(i=1;i<=n;i++)  
            for(j=0;j<=v;j++)  
                if(j-a[i].cost>=0&&f[i-1][j]<f[i-1][j-a[i].cost]+a[i].val)  
                    f[i][j]=f[i-1][j-a[i].cost]+a[i].val;  
                else  
                    f[i][j]=f[i-1][j];  
        printf("%d\n",f[n][v]);  
    }  
    return 0;  

}

<2>多重背包.(没做出来题,只列下思路。)

题意:有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

基本算法:这题目和完全背包问题很类似。基本的方程只需将完全背包问题的方程略微一改即可,因为对于第i种物品有n[i]+1种策略:取0件,取1件……取n[i]件。令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值,则有状态转移方程:f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k<=n[i]}复杂度是O(V*Σn[i])。

<3>完全背包.(依然是只给出基本思路)

题意:有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

基本思路:这个问题非常类似于01背包问题,所不同的是每种物品有无限件。也就是从每种物品的角度考虑,与它相关的策略已并非取或不取两种,而是有取0件、取1件、取2件……等很多种。如果仍然按照解01背包时的思路,令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值。仍然可以按照每种物品不同的策略写出状态转移方程,像这样:

f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k*c[i]<=v}

这跟01背包问题一样有O(VN)个状态需要求解,但求解每个状态的时间已经不是常数了,求解状态f[i][v]的时间是O(v/c[i]),总的复杂度可以认为是O(V*Σ(V/c[i])),是比较大的。

总结解题一般步骤:(1)建立模型,确认状态。(2)找出状态转移方程。(3)找出初始条件。







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值