ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

动态规划重学习笔记

2019-02-27 20:53:49  阅读:251  来源: 互联网

标签:背包 LCS int text 笔记 问题 物品 动态 规划


划水划的太久了,需要做一些复健运动。这篇文章拿来更新最近重新学习DP的笔记。

动态规划

动态规划是用于解决最优化问题的一种方法,而不是某一个算法。对于不同问题,最优解的条件不同,因而有着不同的DP设计方法。因而在实际中必须对具体问题分析处理。

基本模型

动态规划适用于多阶段决策过程的最优化问题。多阶段决策过程,是指问题可以按时间顺序分解成若干相互联系的阶段,在每一个阶段都要做出决策,全部过程的决策是一个决策序列。

在每个阶段所采取的决策,一般来说是与阶段有关的。决策依赖于当前的状态,同时会引起状态的转移。一个决策序列就是在变化的状态中产生出来的,称这种解决多阶段决策最优化的过程为动态规划程序设计方法。

例1 最短路径问题

下图给出了一个地图,地图中的每个顶点代表一个城市,两个城市间的一条连线代表道路,连线上的数值代表道路的长度。现在想从城市A到达城市E,怎样走路程最短?最短路程的长度是多少?

把A到E的全过程分成四个阶段,用K表示阶段变量,第1阶段有一个初始状态A,有两条可供选择的支路A-B1、A-B2;第2阶段有两个初始状态B1、B2,B1有三条可供选择的支路,B2有两条可供选择的支路……。用DK(XI,X+1J)表示在第K阶段由初始状态XI到下阶段的初始状态X+1J的路径距离,FK(XI)表示从第K阶段的XI到终点E的最短距离,利用倒推的方法,求解A到E的最短距离。

对应的C++程序如下:

#include<iostream>
#include<cstring>
using namespace std;
int main()
{
   long d[5][5][5],f[10][10];
   memset(d,42,sizeof(d));          //有些路径是不通的,赋值为较大值,用于判断
   d[1][1][1]=5;d[1][1][2]=3;d[2][1][1]=1;  //以下给可通路径赋正常值
   d[2][1][2]=6;d[2][1][3]=3;d[2][2][2]=8
   d[2][2][4]=4;d[3][1][1]=5;d[3][1][2]=6;
   d[3][2][1]=5;d[3][3][3]=8;d[3][4][3]=3;
   d[4][1][1]=3;d[4][2][1]=4;d[4][3][1]=3; 
   for (int i=0;i<=9;++i)
    for (int j=0;j<=9;++j) f[i][j]=10000000;
   f[5][1]=0;
   for (int i=4;i>=1;--i)
    for (int j=1;j<=4;++j)
     for (int k=1;k<=4;++k)
       if (f[i][j]>d[i][j][k]+f[i+1][k])    //即使走非法路径,也不影响答案
          f[i][j]=d[i][j][k]+f[i+1][k];
    cout<<f[1][1]<<endl;
}

基本概念

  1. 阶段和阶段变量:

    用动态规划求解一个问题时,需要将问题的全过程恰当地分成若干个相互联系的阶段,以便按一定的次序去求解。描述阶段的变量称为阶段变量,通常用K表示,阶段的划分一般是根据时间和空间的自然特征来划分,同时阶段的划分要便于把问题转化成多阶段决策过程,如例题1中,可将其划分成4个阶段,即K = 1,2,3,4。

  2. 状态和状态变量:

    某一阶段的出发位置称为状态,通常一个阶段包含若干状态。一般地,状态可由变量来描述,用来描述状态的变量称为状态变量。如例题1中,C3是一个状态变量。

  3. 决策、决策变量和决策允许集合:

    在对问题的处理中作出的每种选择性的行动就是决策。即从该阶段的每一个状态出发,通过一次选择性的行动转移至下一阶段的相应状态。一个实际问题可能要有多次决策和多个决策点,在每一个阶段的每一个状态中都需要有一次决策,决策也可以用变量来描述,称这种变量为决策变量。在实际问题中,决策变量的取值往往限制在某一个范围之内,此范围称为允许决策集合。如例题1中,F3(C3)就是一个决策变量。

  4. 策略和最优策略:

    所有阶段依次排列构成问题的全过程。全过程中各阶段决策变量所组成的有序总体称为策略。在实际问题中,从决策允许集合中找出最优效果的策略成为最优策略。

  5. 状态转移方程

    前一阶段的终点就是后一阶段的起点,对前一阶段的状态作出某种决策,产生后一阶段的状态,这种关系描述了由k阶段到k+1阶段状态的演变规律,称为状态转移方程。

最优化原理与无后效性

一般来说,能够采用动态规划方法求解的问题,必须满足最优化原理无后效性原则

动态规划的最优化原理

动态规划整个过程的最优策略具有这样的性质:无论过去的状态和决策如何,对前面的决策所形成的状态而言,余下的诸决策必须构成最优策略。这可以理解为子问题的局部最优将导致整个问题的全局最优

动态规划的无后效性原则

无后效性原则指某阶段的状态一旦确定,则此后过程的演变不再受此前各状态及决策的影响。也即「未来与过去无关」。当前的状态是此前历史的一个完整的总结,此前的历史只能通过当前的状态去影响过程未来的演变。

由此可见,对于不能划分阶段的问题,不能运用动态规划来解;对于能划分阶段,但不符合最优化原理的,也不能用动态规划来解;既能划分阶段,又符合最优化原理的,但不具备无后效性原则,还是不能用动态规划来解;误用动态规划程序设计方法求解会导致错误的结果。

动态规划设计方法的一般模式

动态规划所处理的问题是一个多阶段决策问题,一般由初始状态开始,通过对中间阶段决策的选择,达到结束状态;或倒过来,从结束状态开始,通过对中间阶段决策的选择,达到初始状态。这些决策形成一个决策序列,同时确定了完成整个过程的一条活动路线,通常是求最优活动路线。

动态规划的设计都有着一定的模式,一般要经历以下几个步骤:

  1. 划分阶段

    按照问题的时间或空间特征,把问题划分为若干个阶段。在划分阶段时,注意划分后的阶段一定是有序的或者是可排序的,否则问题就无法求解。

  2. 确定状态和状态变量

    将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。当然,状态的选择要满足无后效性。

  3. 确定决策并写出状态转移方程

    因为决策和状态转移有着天然的联系,状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。所以如果确定了决策,状态转移方程也就可以写出。但事实上常常是反过来做,根据相邻两段的各个状态之间的关系来确定决策。

  4. 寻找边界条件

    给出的状态转移方程是一个递推式,需要一个递推的终止条件或边界条件。

动态规划与递推

由于动态规划的“名气”如此之大,以至于很多人甚至一些资料书上都往往把一种与动态规划十分相似的算法,当作是动态规划。这种算法就是递推。实际上,这两种算法还是很容易区分的。

按解题的目标来分,信息学试题主要分四类:判定性问题、构造性问题、计数问题和最优化问题。我们在竞赛中碰到的大多是最优化问题,而动态规划正是解决最优化问题的有力武器,因此动态规划在竞赛中的地位日益提高。而递推法在处理判定性问题和计数问题方面也是一把利器。下面分别就两个例子,谈一下递推法和动态规划在这两个方面的联系。

例9 最长公共子序列

一个给定序列的子序列是在该序列中删去若干元素后得到的序列。确切地说,若给定序列\(X=<x_1,x_2,\cdots ,x_m>​\) ,则另一序列\(Z=<z_1,\ z_2,\ \cdots ,\ z_k>​\) 是\(X​\)的子序列是指存在一个严格递增的下标序列\(<i_1,i_2,\cdots,i_k>​\),使得对于所有\(j=1,2,\cdots,k​\) 有 \(X_{ij}=Zj​\)。

给定两个序列\(X=<x_1, x_2, \cdots , x_m>\)和\(Y=<y_1,y_2, \cdots ,y_n>\).要求找出\(X\)和\(Y\)的一个最长公共子序列。

【输入】

共有两行。每行为一个由大写字母构成的长度不超过1000的字符串,表示序列X和Y。

【输出】

第一行为一个非负整数。表示所求得的最长公共子序列的长度。若不存在公共子序列.则输出文件仅有一行输出一个整数0。

【输入样例】

ABCBDAB
BDCABA

【输出样例】

4

思路分析:

这一题最容易想到的是暴力搜索。对于X的每一个子序列,都去判断它是否也为Y的一个子序列,在过程中选出最长的公共子序列。算法用时\(O (2^m \times 2^n)​\).

事实上最长公共子序列问题具有最优子结构性质,因而可以用动态规划算法求解。

我们记\(X_i​\) 为\(X​\)序列的前\(i​\)个字符(\(1 \leq i \leq m​\)), \(Y_j​\)为\(Y​\)序列的前\(j​\)个字符(\(1 \leq j \leq n​\)). 假定\(Z = <z_1,\ z_2,\ \cdots,\ z_k> \in \text{LCS}(X,Y)​\). 有:

  • 若\(x_m = y_n​\),即两个给定序列最后一个字符相同,则不难用反证法证明:该字符必是X与Y的任一最长公共子序列Z(设长度为k)的最后一个字符,即有\(z_k = x_m = y_n​\) 且显然有\(Z_{k-1}\in \text{LCS}(X_{m-1} , Y_{n-1})​\)即Z的前缀\(Z_{k-1}​\)是\(X_{m-1}​\)与\(Y_{n-1}​\)的最长公共子序列。此时,问题化归成求\(X_{m-1}​\)与\(Y_{n-1}​\)的最长公共子序列(\(\text{LCS}(X,Y)​\)的长度等于\(\text{LCS}(X_{m-1} , Y_{n-1})​\)的长度加1)。

  • 若\(x_m \neq y_n​\),则要么\(Z\in \text{LCS}(X_{m-1} , Y)​\),要么\(Z\in \text{LCS}(X , Y_{n-1})​\)。此时\(z_k \neq x_m​\) 与 \(z_k \neq y_n​\) 至少有一个成立。若\(z_k \neq x_m​\)则\(Z\in \text{LCS}(X_{m-1} , Y)​\),若\(z_k \neq y_n​\) 则\(Z\in \text{LCS}(X , Y_{n-1})​\)。**故问题变为求$ \text{LCS}(X_{m-1} , Y)​\(与\)\text{LCS}(X , Y_{n-1})​\(。** LCS(X , Y)的长度为:\)\max{ \text{LCS}(X_{m-1}, Y)\text{的长度}, \text{LCS}(X , Y_{n-1})\text{的长度}}​$。

由于上述当xm≠yn的情况中,求LCS(Xm-1 , Y)的长度与LCS(X , Yn-1)的长度,这两个问题不是相互独立的:两者都需要求LCS(Xm-1,Yn-1)的长度。另外两个序列的LCS中包含了两个序列的前缀的LCS,故问题具有最优子结构性质考虑用动态规划法。

也就是说,解决这个LCS问题,你要求三个长度:

  1. \(\text{LCS}(X_{m-1} , Y_{n-1}) \text{的长度}+ 1​\)
  2. \(\text{LCS}(X_{m-1}, Y)\text{的长度}, \text{LCS}(X , Y_{n-1})\text{的长度}\)
  3. \(\max\{ \text{LCS}(X_{m-1}, Y)\text{的长度}, \text{LCS}(X , Y_{n-1})\text{的长度}\}\)。

代码如下(YBT1265):

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define MAXN 1010

char str1[ MAXN ], str2[ MAXN ];
int f[ MAXN ][ MAXN ]; // f[i][j] 存储字符串1从起始到i位置 字符串1从起始到j位置的LCS最大长度

int main()
{
    scanf("%s", str1+1);
    scanf("%s", str2+1);

    memset( f, 0, sizeof( f ));

    int len1 = strlen(str1+1), len2 = strlen(str2+1);

    for( int i = 1; i <= len1; i++ ){
        for( int j = 1; j <= len2; j++ ){
            if( str1[i] == str2[j] ){ // 情况1 末尾相同
                f[i][j] = f[i-1][j-1] + 1;
            }
            else{ // 情况2 末尾不同
                f[i][j] = max(f[i-1][j], f[i][j-1]);
            }
        }
    }
    printf("%d\n", f[len1][len2]);
    return 0;
}

背包问题

01背包

有N件物品和一个容量为V的背包。第i件物品的费用(即体积,下同)是w[i],价值是c[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。

用子问题定义状态:即f[i][v]表示前i件物品(部分或全部)恰放入一个容量为v的背包可以获得的最大价值。则其状态转移方程便是:f[i][v]=max{f[i-1][v],f[i-1][v-w[i]]+c[i]}

子问题「将前i件物品放入容量为v的背包中」所要决策的是对于第i件物品,不放入背包这两种选择。因而只涉及到已经讨论完的i-1种物品,即等同于「将前i-1件物品放入剩下的容量为v-w[i]的背包中」,此时背包最大价值为f[i-1][v-w[i]]再加上通过放入第i件物品获得的价值c[i]不放等同于「将前i-1件物品放入容量为v的背包中」,背包价值依旧为f[i-1][v]

基本上所有有关背包问题的方程都由这个方程衍生而来。

注意f[i][v]有意义当且仅当存在一个前i件物品的子集,其费用总和为v。所以按照这个方程递推完毕后,最终的答案并不一定是f[N][V],而是f[N][0..V]的最大值。如果将状态的定义中的“恰”字去掉,在转移方程中就要再加入一项f[i-1][v],这样就可以保证f[N][V]就是最后的答案。但是若将所有f[i][j]的初始值都赋为0,你会发现f[n][v]也会是最后的答案。为什么呢?因为这样你默认了最开始f[i][j]是有意义的,只是价值为0,就看作是无物品放的背包价值都为0,所以对最终价值无影响,这样初始化后的状态表示就可以把“恰”字去掉。

优化空间复杂度

以上方法的时间和空间复杂度均为O(N*V),其中时间复杂度基本已经不能再优化了,但空间复杂度却可以优化到O(V)。

在上文的基本思路中,我们的主循环i = 1 -> n,从而计算出二维数组f[i][j = 0...v] 的所有值,那么可以用一维数组f[0...v]实现吗?在二维数组的实现中,f[i][v] 是由 f[i-1][v]f[i-1][v-w[i]]两个子问题得来,那么如果要用一维数组实现,我们必须要保证在求f[i][v]f[i-1][v]f[i-1][v-w[i]]的值没有被覆盖。因此,我们要在每次主循环中v = v->0的逆序去求f[v],这样才能保证上述条件。

此时的转移方程为

for i = 1 ... N
    for v = V ... 0
        f[v] = max{f[v], f[v-w[i] + c[i]}
例1 01背包

【问题描述】

一个旅行者有一个最多能用m公斤的背包,现在有n件物品,它们的重量分别是W1,W2,...,Wn,它们的价值分别为C1,C2,...,Cn.若每种物品只有一件,求旅行者能获得最大总价值。

【输入格式】

第一行:两个整数,M(背包容量,M<=200)和N(物品数量,N<=30);

第2..N+1行:每行二个整数Wi,Ci,表示每个物品的重量和价值。

【输出格式】

仅一行,一个数,表示最大总价值。

【样例输入】package.in

10 4
2  1
3  3
4  5
7  9

【样例输出】package.out

12

一维代码:

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
int f[ 220 ] = {0};

int main()
{
    int M, N;
    scanf("%d%d", &M, &N);

    int w, c;
    for( int i = 1; i <= N; i++ ){
        scanf("%d%d", &w, &c);

        for( int j = M; j >= 0; j-- ){
            if( j-w >= 0 )
                f[j] = max( f[j], f[j-w] + c );
        }
    }

    printf("%d\n", f[M]);

    return 0;
}

总结

01背包问题是最基本的背包问题,它包含了背包问题中设计状态、方程的最基本思想,另外,别的类型的背包问题往往也可以转换成01背包问题求解。故一定要仔细体会上面基本思路的得出方法,状态转移方程的意义,以及最后怎样优化的空间复杂度。

完全背包

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

这个问题类似与01背包,不同的是每件物品的总量无限,即可以取0, 1, 2, ... 件。按照01背包的思路,便有f[i][v] = max{f[i-1][ v - k*w[i] ] + k*c[i] | 0 <= k*w[i] <= v}

而对于压缩空间的一维数组实现,算法伪代码则为

for i = 1 ... N
    for v = 0 ... V
        f[v] = max{f[v], f[v-w[i] + c[i]}

这里变动只有v的循环顺序变为从0到V。为什么这样可行呢?

在01背包中,v从V到0循环是为了保证f[i][v]是由f[i-1][v-w[i]所推出,即保证了每件物品只选择一次。而完全背包中每种物品可选择无限件,因而可以从已经选过这种物品的背包中再加一件这种物品,这种策略使得必须采用v=0...V这种循环。

例2 完全背包问题 knapsack

【问题描述】

  设有n种物品,每种物品有一个重量及一个价值。但每种物品的数量是无限的,同时有一个背包,最大载重量为M,今从n种物品中选取若干件(同一种物品可以多次选取),使其重量的和小于等于M,而价值的和为最大。

【输入格式】

第一行:两个整数,M(背包容量,M<=200)和N(物品数量,N<=30);

第2..N+1行:每行二个整数Wi,Ci,表示每个物品的重量和价值。

【输出格式】

仅一行,一个数,表示最大总价值。

【样例输入】knapsack.in

10 4
2 1
3 3
4 5
7 9

【样例输出】knapsack.out

max=12

代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
int f[ 220 ] = {0};

int main()
{
    int M, N;
    scanf("%d%d", &M, &N);

    int w, c;
    for( int i = 1; i <= N; i++ ){
        scanf("%d%d", &w, &c);

        for( int j = 0; j <= M; j++ ){
            if( j-w >= 0 )
                f[j] = max( f[j], f[j-w] + c );
        }
    }

    printf("max=%d\n", f[M]);

    return 0;
}

一个简单有效的优化

完全背包问题有一个很简单有效的优化,是这样的:若两件物品ij满足\(w_i \leq w_j\)且\(c_i \geq c_j\),则将物品j去掉,不用考虑。这个优化的正确性显然:任何情况下都可将价值小费用高的j换成物美价廉的i,得到至少不会更差的方案。对于随机生成的数据,这个方法往往会大大减少物品的件数,从而加快速度。然而这个并不能改善最坏情况的复杂度,因为有可能特别设计的数据可以一件物品也去不掉。

转化为01背包问题求解

既然01背包问题是最基本的背包问题,那么我们可以考虑把完全背包问题转化为01背包问题来解。最简单的想法是,考虑到第i种物品最多选V/w[i]件,于是可以把第i种物品转化为V/w[i]件费用及价值均不变的物品,然后求解这个01背包问题。这样完全没有改进基本思路的时间复杂度,但这毕竟给了我们将完全背包问题转化为01背包问题的思路:将一种物品拆成多件物品。

更高效的转化方法是:把第i种物品拆成费用为\(w_i \times 2^k\)、价值为\(c_i \times 2^k\)的若干件物品,其中\(k\)满足\(w_i \times 2^k < V\)。这是二进制的思想,因为不管最优策略选几件第i种物品,总可以表示成若干个\(2^k\)件物品的和。这样把每种物品拆成\(\log(\frac{V}{w_i})+1\)件物品,是一个很大的改进。

总结

完全背包问题也是一个相当基础的背包问题,它有两个状态转移方程,分别在“基本思路”以及“O(VN)的算法“的小节中给出。希望你能够对这两个状态转移方程都仔细地体会,不仅记住,也要弄明白它们是怎么得出来的,最好能够自己想一种得到这些方程的方法。事实上,对每一道动态规划题目都思考其方程的意义以及如何得来,是加深对动态规划的理解、提高动态规划功力的好方法。

多重背包问题

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

基本算法思路

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

转化为01背包问题

把第i种物品换成n[i]件01背包中的物品,则得到了物品数为∑n[i]的01背包问题,直接求解,复杂度仍然是\(O(V \times \sum n_i)\)。

但是我们期望将它转化为01背包问题之后能够像完全背包一样降低复杂度。仍然考虑二进制的思想,我们考虑把第i种物品换成若干件物品,使得原问题中第i种物品可取的每种策略——取0..n[i]件——均能等价于取若干件代换以后的物品。另外,取超过n[i]件的策略必不能出现。

方法是:将第i种物品分成若干件物品,其中每件物品有一个系数,这件物品的费用和价值均是原来的费用和价值乘以这个系数。使这些系数分别为1,2,4,...,\(2^{(k-1)}\),\(n_i-2^k+1​\),且k是满足n[i]-2^k+1>0的最大整数(注意:这些系数已经可以组合出1~n[i]内的所有数字)。例如,如果n[i]为13,就将这种物品分成系数分别为1,2,4,6的四件物品。

分成的这几件物品的系数和为n[i],表明不可能取多于n[i]件的第i种物品。另外这种方法也能保证对于0..n[i]间的每一个整数,均可以用若干个系数的和表示。这样就将第i种物品分成了\(\log n_i​\)种物品,将原问题转化为了复杂度为\(O(V \times \sum \log{n_i})​\)的01背包问题,是很大的改进。

例3 庆功会 party

【问题描述】

为了庆贺班级在校运动会上取得全校第一名成绩,班主任决定开一场庆功会,为此拨款购买奖品犒劳运动员。期望拨款金额能购买最大价值的奖品,可以补充他们的精力和体力。

【输入格式】

第一行二个数n(n<=500),m(m<=6000),其中n代表希望购买的奖品的种数,m表示拨款金额。
接下来n行,每行3个数,v、w、s,分别表示第I种奖品的价格、价值(价格与价值是不同的概念)和购买的数量(买0件到s件均可),其中v<=100,w<=1000,s<=10。

【输出格式】

第一行:一个数,表示此次购买能获得的最大的价值(注意!不是价格)。

【输入样例】

5 1000
80 20 4
40 50 9
30 50 7
40 30 6
20 20 1

【输出样例】

1040

参考代码1 朴素算法

#include<cstdio>
using namespace std;

int v[6002], w[6002], s[6002];
int f[6002];
int n, m;

int max(int x,int y)  {
    if (x < y) return y;
      else return x; 
}

int main(){
    scanf("%d%d",&n,&m);
    for (int i = 1; i <= n; i++)
        scanf("%d%d%d",&v[i],&w[i],&s[i]);
    for (int i = 1; i <= n; i++)
       for (int j = m; j >= 0; j--)
          for (int k = 0; k <= s[i]; k++){
               if (j-k*v[i]<0) break;
               f[j] = max(f[j],f[j-k*v[i]]+k*w[i]);
           }
    printf("%d",f[m]);
    return 0;
} 

代码二 二进制优化

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
int f[ 6010 ] = {0};

int main()
{
    int M, N;
    scanf("%d%d", &N , &M);

    int w, v, s;
    int part_weight, part_value;
    for( int i = 1; i <= N; i++ ){
        scanf("%d%d%d", &w, &v, &s);

        // 拆分成不同个数的物品
        int k;
        for( k = 1; s - (1 << k) + 1 > 0; k++){
            part_weight = w * (1 << (k-1));
            part_value = v * (1 << (k-1));

            // 01 背包求解
            for( int j = M; j >= 0; j-- ){
                if( j-part_weight >= 0 )
                    f[j] = max( f[j], f[j-part_weight] + part_value );
            }
        }

        // 最后一种
        part_weight = w * (s - (1 << (k-1)) + 1);
        part_value = v * (s - (1 << (k-1)) + 1);
        for( int j = M; j >= 0; j-- ){
            if( j-part_weight >= 0 )
                f[j] = max( f[j], f[j-part_weight] + part_value );
        }
    }

    printf("%d\n", f[M]);

    return 0;
}

总结

这里我们看到了将一个算法的复杂度由\(O(V \times \sum n_i)\)改进到\(O(V \times \sum \log{n_i})\)的过程,还知道了存在应用超出NOIP范围的知识的O(VN)算法。希望你特别注意「拆分物品」的思想和方法,自己证明一下它的正确性,并用尽量简洁的程序来实现。

标签:背包,LCS,int,text,笔记,问题,物品,动态,规划
来源: https://www.cnblogs.com/FrozenApple/p/10446759.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有