zoukankan      html  css  js  c++  java
  • 算法-动态规划(01背包)

    最近闲来无事,学学算法。

    什么是动态规划,我们要如何描述它?

    动态规划算法通常基于一个递推公式及一个或多个初始状态。当前子问题的解将由上一次子问题的解推出。使用动态规划来解题只需要多项式时间复杂度,因此它比回溯法、暴力法等要快许多。

    这里所说的子问题并不一定指前一个问题的解

    比如说1+2+3+。。。+100,我们这里可以sum[i]+100来求出总和,而sum[i]是1加到i的总和,这个问题就是1加到99的子问题再加上100就得到。

    动态规划则并非是如此,它可能跟前面所有的子问题的某个解有联系,举个例子:

    如果我们有面值为1元、3元和5元的硬币若干枚,如何用最少的硬币凑够11元?

    (表面上这道题可以用贪心算法,但贪心算法无法保证可以求出解,比如1元换成2元的时候)

    首先我们思考一个问题,如何用最少的硬币凑够i元(i<11)?为什么要这么问呢?两个原因:1.当我们遇到一个大问题时,总是习惯把问题的 规模变小,这样便于分析讨论。 2.这个规模变小后的问题和原来的问题是同质的,除了规模变小,其它的都是一样的,本质上它还是同一个问题(规模变小后的问题其实是原问题的子问题)。

    好了,让我们从最小的i开始吧。当i=0,即我们需要多少个硬币来凑够0元。由于1,3,5都大于0,即没有比0小的币值,因此凑够0元我们最少需 要0个硬币。 (这个分析很傻是不是?别着急,这个思路有利于我们理清动态规划究竟在做些什么。) 这时候我们发现用一个标记来表示这句“凑够0元我们最少需要0个硬币。”会比较方便,如果一直用纯文字来表述,不出一会儿你就会觉得很绕了。那么,我们用 d(i)=j来表示凑够i元最少需要j个硬币。于是我们已经得到了d(0)=0,表示凑够0元最小需要0个硬币。当i=1时,只有面值为1元的硬币可用, 因此我们拿起一个面值为1的硬币,接下来只需要凑够0元即可,而这个是已经知道答案的,即d(0)=0。所 以,d(1)=d(1-1)+1=d(0)+1=0+1=1。当i=2时,仍然只有面值为1的硬币可用,于是我拿起一个面值为1的硬币,接下来我只需要再 凑够2-1=1元即可(记得要用最小的硬币数量),而这个答案也已经知道了。所以d(2)=d(2-1)+1=d(1)+1=1+1=2。一直到这里,你 都可能会觉得,好无聊,感觉像做小学生的题目似的。因为我们一直都只能操作面值为1的硬币!耐心点,让我们看看i=3时的情况。当i=3时,我们能用的硬 币就有两种了:1元的和3元的( 5元的仍然没用,因为你需要凑的数目是3元!5元太多了亲)。既然能用的硬币有两种,我就有两种方案。如果我拿了一个1元的硬币,我的目标就变为了:凑够 3-1=2元需要的最少硬币数量。即d(3)=d(3-1)+1=d(2)+1=2+1=3。这个方案说的是,我拿3个1元的硬币;第二种方案是我拿起一 个3元的硬币,我的目标就变成:凑够3-3=0元需要的最少硬币数量。即d(3)=d(3-3)+1=d(0)+1=0+1=1. 这个方案说的是,我拿1个3元的硬币,这里d(3)值跟d(2)是没有关系的,如果你还不理解,下面我们结合01背包来说明。好了,这两种方案哪种更优呢?记得我们可是要用最少的硬币数量来凑够3元的。所以,选择d(3)=1,怎么来的呢? 具体是这样得到的:d(3)=min{d(3-1)+1, d(3-3)+1}。

    状态和状态转移方程

    从以上的文字中,我们要抽出动态规划里非常重要的两个概念:状态和状态转移方程。

    状态:就是上面d(3)d(2)等,抽象d(i);

    状态转移方程:d(3)=min{d(3-1)+1, d(3-3)+1},抽象d(i)=min{ d(i-vj)+1 },其中i-vj >=0,vj表示第j个硬币的面值;

    01背包问题

    问题描述:有N件物品和一个容量为V的背包。第i件物品的体积是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。

    状态转移方程:f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}

    这个方程非常重要,基本上所有跟背包相关的问题的方程都是由它衍生出来的
    伪码:
      for i=1..N
       for v=V..0
        f[v]=max{f[v],f[v-c[i]]+w[i]};
    如果不放第i件物品,那么问题就转化为“前i-1件物品放入容量为v的背包中”,
    价值为f[i-1][v];
    如果放第i件物品,那么问题就转化为“前i-1件物品放入剩下的容量为v-c[i]的背包中”,
    此时能获得的最大价值就是f[i-1][v-c[i]]再加上通过放入第i件物品获得的价值w[i]。

    如果一开始看01背包这里最容易混淆,我就是在这里迷糊好久,要特别如果不明白要特别注意上面粗体的字,它是先放第i件,然后剩下容量再放前面i-1件,然而剩下容量所装的物品,我们前面都已求出,下面,让我们根据代码来理解;

    Input输入的第一行有两个整数T(1 <= T <= 1000)和M(1 <= M <= 100),用一个空格隔开,T代表背包容量,M代表物品数目。接下来的M行每行包括两个在1到100之间(包括1和100)的整 数,分别表示每个物品的体积和价值。
    Output输出包括一行,这一行只包含一个整数,装入背包可使价值总和最大。
    Sample Input
    70 3
    71 100
    69 1
    1 2
    Sample Output
    3

    复制代码
    #include<iostream>
    # include<cstring>
    # define max(a,b) a>b?a:b
    using namespace std;
    int main()
    {
    
        int dp[101][1001],m,T,w[101],val[101],i,j;
        cin>>T>>m;
        for(i=1;i<=m;i++)
            cin>>w[i]>>val[i];
        memset(dp,0,sizeof(dp));
        for(i=1;i<=m;i++)
    { for(j=0;j<=T;j++)//j相当于上面说的V-c[i],这里的循环都是为后面解准备的 { if(j>=w[i]) dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+val[i]);//放还是不放的选择 else dp[i][j]=dp[i-1][j];
      cout<<dp[i][j]<<" "; }
    cout<<endl;
    } cout<<dp[m][T]<<endl; return 0; }
    复制代码

    这里就测试一下,
    10 4
    5 10
    8 16
    5 5
    10 10

    如果要符合上面题目输出要求,把红色代码去掉

  • 相关阅读:
    [编程题] 回文数问题
    [编程题] 以字符串的形式读入两个数字,再以字符串的形式输出两个数字的和
    [编程题] 合并有序链表
    [编程题] 借用栈实现链表反转
    [编程题] 二叉树求深度
    pat 甲级 1045 ( Favorite Color Stripe ) (动态规划 )
    pat 甲级 1034 ( Head of a Gang )
    PAT public bike management (30)
    PAT 甲练习 1003 Emergency
    vim 自动代码格式调整
  • 原文地址:https://www.cnblogs.com/lsgsanxiao/p/5195446.html
Copyright © 2011-2022 走看看