动态规划问题

来源:互联网 发布:cpld程序下载软件 编辑:程序博客网 时间:2024/06/03 11:51

适合采用DP方法的最优化问题中的两个要素:最优子结构和重叠子结构问题。
备忘录方法用来充分利用重叠子问题性质
最优子结构:
用DP求解优化问题的第一步是描述最优解的结构:若问题的一个最优解中包含了子问题的最优解,则该问题具有最优子结构(该情况下贪心算法也适用)。
找寻最优子结构遵循的共同模式:
    1.问题的一个解可以是做一个选择。做这种选择会得到一个或者多个有待解决 的子问题
    2.假设对一个给定的问题,已知的是一个可以可以导致最优解的选择。不必关心如何确定这个选择,尽管假定他是已知的。
    3.在已知这个选择后,要确定那些子问题会随之发生,以及如何最好的描述所得到的子问题空间。
    4.利用一种剪贴技术,来证明在问题的一个最优解中,使用的子问题的解本身也必须是最优的。通过假设每一个子问题的解都不是最优解,然后到处矛盾,即可做到这一点。特别的,通过剪除非最优的子问题解再贴上最优解,就证明了可以得到原问题的一个更好的解,因此,这与假设已经得到一个最优解相矛盾。如果有多于一个的子问题的话,由于它们通常非常类似,所以只要对其中一个子问题的剪贴处理略加修改,即可很容易的用于其他子问题。


1、求最长子序列

问题描述:

子串应该比较好理解,至于什么是子序列,这里给出一个例子:有两个母串

      asdfhg

      zxdfgn

比如df、fg、ag在两个字符串中都出现过并且出现顺序与母串保持一致,我们将其称为公共子序列。最长公共子序列(Longest Common Subsequence, LCS),顾名思义,是指在所有的子序列中最长的那一个。子串是要求更严格的一种子序列,要求在母串中连续地出现。在上述例子的中,最长公共子序列为dfg。

求解算法

对于母串X=<x1,x2,⋯,xm>X=<x1,x2,⋯,xm>, Y=<y1,y2,⋯,yn>Y=<y1,y2,⋯,yn>,求LCS与最长公共子串。

暴力解决:假设 m<nm<n, 对于母串XX,我们可以暴力找出2m2m个子序列,然后依次在母串YY中匹配,算法的时间复杂度会达到指数级O(n∗2m)O(n∗2m)。显然,暴力求解不太适用于此类问题。

动态规划假设Z=<z1,z2,⋯,zk>Z=<z1,z2,⋯,zk>是XX与YY的LCS, 我们观察到如果xm=ynxm=yn,则zk=xm=ynzk=xm=yn,有Zk−1Zk−1是Xm−1Xm−1与Yn−1Yn−1的LCS;如果xm≠ynxm≠yn,则ZkZk是XmXm与Yn−1Yn−1的LCS,或者是Xm−1Xm−1与YnYn的LCS。因此,求解LCS的问题则变成递归求解的两个子问题。但是,上述的递归求解的办法中,重复的子问题多,效率低下。改进的办法——用空间换时间,用数组保存中间状态,方便后面的计算。这就是动态规划(DP)的核心思想了。

DP求解LCSdp[i][j]:dp[i][j]表示长度分别为i和j的序列X和序列Y构成的LCS的长度
dp[i][j] = 0,如果i=0 或 j=0 
dp[i][j] = dp[i-1][j-1] + 1,如果 X[i-1] = Y[i-1] 
dp[i][j] = max{ dp[i-1][j], dp[i][j-1] },如果 X[i-1] != Y[i-1]

LCS长度为 dp[Xlen][Ylen]

// test1.cpp : 定义控制台应用程序的入口点。//#include "stdafx.h"#include<stdio.h>#include<iostream>using namespace std;void LCS_dp(char *x, char*y);int dp[100][100];  // 存储LCS长度, 下标i,j表示序列X,Y长度int _tmain(int argc, _TCHAR* argv[]){ char *x = "asdfbgh";char *y = "zxcdfgn";LCS_dp(x, y);return 0;}void LCS_dp(char * X, char * Y){int i, j;int xlen = strlen(X);int ylen = strlen(Y);// dp[0-xlen][0] & dp[0][0-ylen] 都已初始化0     for (i = 1; i <= xlen; ++i){for (j = 1; j <= ylen; ++j){if (X[i - 1] == Y[j - 1]){dp[i][j] = dp[i - 1][j - 1] + 1;}else if (dp[i][j - 1] > dp[i - 1][j]){dp[i][j] = dp[i][j - 1];}else{dp[i][j] = dp[i - 1][j];}}}printf("len of LCS is: %d\n", dp[xlen][ylen]);i = xlen;j = ylen;int k = dp[i][j];char lcs[100] = { '\0' };while (i && j){if (X[i - 1] == Y[j - 1] && dp[i][j] == dp[i - 1][j - 1] + 1){lcs[--k] = X[i - 1];--i; --j;}else if (X[i - 1] != Y[j - 1] && dp[i - 1][j] > dp[i][j - 1]){--i;}else{--j;}}printf("%s\n", lcs);}


2.求最长递增子序列

给定一个长度为N的数组,找出一个最长的单调自增子序列(不一定连续,但是顺序不能乱)。例如:给定一个长度为6的数组A{5, 6, 7, 1, 2, 8},则其最长的单调递增子序列为{5,6,7,8},长度为4.

解法1:最长公共子序列法

这个问题可以转换为最长公共子序列问题。如例子中的数组A{5,6, 7, 1, 2, 8},则我们排序该数组得到数组A‘{1, 2, 5, 6, 7, 8},然后找出数组A和A’的最长公共子序列即可。显然这里最长公共子序列为{5, 6, 7, 8},也就是原数组A最长递增子序列。最长公共子序列算法在算法导论上有详细讲解,这里简略说下思想。

假定两个序列为X={x1, x2, ..., xm}和Y={y1, y2, ..., yn),并设Z={z1, z2, ..., zk}为X和Y的任意一个LCS。

1)如果xm = yn,则zk = xm=yn,且Zk-1是Xm-1和Yn-1的一个LCS。

2)如果xm != yn, 则zk != xm蕴含Z是Xm-1和Y得一个LCS。

3)如果xm != yn, 则zk != yn蕴含Z是X和Yn-1的一个LCS。

解法2:动态规划法(时间复杂度O(N^2))

设长度为N的数组为{a0,a1, a2, ...an-1),则假定以aj结尾的数组序列的最长递增子序列长度为L(j),则L(j)={ max(L(i))+1, i<j且a[i]<a[j] }。也就是说,我们需要遍历在j之前的所有位置i(从0到j-1),找出满足条件a[i]<a[j]的L(i),求出max(L(i))+1即为L(j)的值。最后,我们遍历所有的L(j)(从0到N-1),找出最大值即为最大递增子序列。时间复杂度为O(N^2)。

如在序列1,-1,2,-3,4,-5,6,-7中,最长递增序列为1,2,4,6。

时间复杂度O(N^2)的算法:
LIS[i]:表示数组前i个元素中(包括第i个),最长递增子序列的长度
LIS[i] = max{ 1, LIS[k]+1 }, 0 <= k < i, a[i]>a[k]


#include "stdafx.h"#include<iostream>using namespace std;int find_max_len_method(const int arr[], int length){int *max_len = new int[length];for (int i = 0; i < length; i++){max_len[i] = 1;for (int j = 0; j < i; j++){if (arr[i]>arr[j] && max_len[j] + 1>max_len[i])max_len[i] = max_len[j] + 1;}}cout << "max_len:" << endl;for (int i = 0; i < length; i++)cout << max_len[i] << "\t";cout << endl;int result = 0;for (int i = 0; i < length; i++){if (max_len[i]>result){result = max_len[i];}}delete[] max_len;return result;}int _tmain(int argc, _TCHAR* argv[]){int max_lis;int a[] = { 1, -1, 2, -3, 4, -5, 6, -7 };cout << "output array:" << endl;for (int i = 0; i < (sizeof(a))/(sizeof(int)); i++)cout << a[i] << "\t";cout << endl;max_lis = find_max_len_method(a, (sizeof(a)) / (sizeof(int)));cout << "LIS is:" << max_lis << endl;system("PAUSE");return 0;}


3.计算两个字符相似度

许多程序会大量使用字符串。对于不同的字符串,我们希望能够有办法判断其相似程序。我们定义一套操作方法来把两个不相同的字符串变得相同,具体的操作方法为:
1.修改一个字符(如把“a”替换为“b”);  
2.增加一个字符(如把“abdd”变为“aebdd”);
3.删除一个字符(如把“travelling”变为“traveling”);
比如,对于“abcdefg”和“abcdef”两个字符串来说,我们认为可以通过增加/减少一个“g”的方式来达到目的。上面的两种方案,都仅需要一 次 。把这个操作所需要的次数定义为两个字符串的距离,而相似度等于“距离+1”的倒数。也就是说,“abcdefg”和“abcdef”的距离为1,相似度 为1/2=0.5。
给定任意两个字符串,你是否能写出一个算法来计算它们的相似度呢?
原文的分析与解法  
   不难看出,两个字符串的距离肯定不超过它们的长度之和(我们可以通过删除操作把两个串都转化为空串)。虽然这个结论对结果没有帮助,但至少可以知道,任意两个字符串的距离都是有限的。我们还是就住集中考虑如何才能把这个问题转化成规模较小的同样的子问题。如果有两个串A=xabcdae和B=xfdfa,它们的第一个字符是 相同的,只要计算A[2,...,7]=abcdae和B[2,...,5]=fdfa的距离就可以了。但是如果两个串的第一个字符不相同,那么可以进行 如下的操作(lenA和lenB分别是A串和B串的长度)。
1.删除A串的第一个字符,然后计算A[2,...,lenA]和B[1,...,lenB]的距离。
2.删除B串的第一个字符,然后计算A[1,...,lenA]和B[2,...,lenB]的距离。
3.修改A串的第一个字符为B串的第一个字符,然后计算A[2,...,lenA]和B[2,...,lenB]的距离。
4.修改B串的第一个字符为A串的第一个字符,然后计算A[2,...,lenA]和B[2,...,lenB]的距离。
5.增加B串的第一个字符到A串的第一个字符之前,然后计算A[1,...,lenA]和B[2,...,lenB]的距离。
6.增加A串的第一个字符到B串的第一个字符之前,然后计算A[2,...,lenA]和B[1,...,lenB]的距离。
在这个题目中,我们并不在乎两个字符串变得相等之后的字符串是怎样的。所以,可以将上面的6个操作合并为:
1.一步操作之后,再将A[2,...,lenA]和B[1,...,lenB]变成相字符串。
2.一步操作之后,再将A[2,...,lenA]和B[2,...,lenB]变成相字符串。
3.一步操作之后,再将A[1,...,lenA]和B[2,...,lenB]变成相字符串。
通过以上1和6,2和5,3和4的结合操作,最后两个字符串每个对应的字符会相同,但是这三种操作产生的最终的两个字符串是不一样的。我们不知道通过上述的三种结合那种使用的操作次数是最少的。所以我们要比较操作次数来求得最小值。


下面给出应用动态规划解决字符串相似度的解释和程序源码.
设Ai为字符串A(a1a2a3 … am)的前i个字符(即为a1,a2,a3 … ai)
设Bj为字符串B(b1b2b3 … bn)的前j个字符(即为b1,b2,b3 … bj)
设 L(i,j)为使两个字符串和Ai和Bj相等的最小操作次数。
当ai==bj时 显然 L(i,j) = L(i-1,j-1)
当ai!=bj时 
 若将它们修改为相等,则对两个字符串至少还要操作L(i-1,j-1)次
 若删除ai或在bj后添加ai,则对两个字符串至少还要操作L(i-1,j)次
 若删除bj或在ai后添加bj,则对两个字符串至少还要操作L(i,j-1)次
 此时L(i,j) = min( L(i-1,j-1), L(i-1,j), L(i,j-1) ) + 1 
显然,L(i,0)=i,L(0,j)=j, 再利用上述的递推公式,可以直接计算出L(i,j)值。L(i,0)代表Ai和B0,如果想把一个字符串和一个空字符串变的相同,那么之后删除非空串中的字符或者把空串变成和非空串相同的字符串,那么所需要操作的次数为i次。








下面给出应用动态规划解决字符串相似度的解释和程序源码.
设Ai为字符串A(a1a2a3 … am)的前i个字符(即为a1,a2,a3 … ai)
设Bj为字符串B(b1b2b3 … bn)的前j个字符(即为b1,b2,b3 … bj)
设 L(i,j)为使两个字符串和Ai和Bj相等的最小操作次数。
当ai==bj时 显然 L(i,j) = L(i-1,j-1)
当ai!=bj时 
 若将它们修改为相等,则对两个字符串至少还要操作L(i-1,j-1)次
 若删除ai或在bj后添加ai,则对两个字符串至少还要操作L(i-1,j)次
 若删除bj或在ai后添加bj,则对两个字符串至少还要操作L(i,j-1)次
 此时L(i,j) = min( L(i-1,j-1), L(i-1,j), L(i,j-1) ) + 1 

显然,L(i,0)=i,L(0,j)=j, 再利用上述的递推公式,可以直接计算出L(i,j)值。L(i,0)代表Ai和B0,如果想把一个字符串和一个空字符串变的相同,那么之后删除非空串中的字符或者把空串变成和非空串相同的字符串,那么所需要操作的次数为i次。

#include <iostream>#include<string.h>#include<stdio.h>#include<stdlib.h>using namespace std;int  minnum(int a,int b,int c){    return (((a<b)?a:b)<c)?((a<b)?a:b):c;}int caculate_distance(string str1,string str2){    int d;    int str1_len = (int)str1.length()+1;    int str2_len = (int)str2.length()+1;    int **dist;    dist = new int*[str1_len];    for(int i=0;i<str1_len;i++)        dist [i] = new int[str2_len];    for(int i = 1;i <str1_len;i++)    {        dist[i][0] = i;    }    cout<<"there is ok!!!"<<endl;    for(int j = 1;j<str2_len;j++)    {        dist[0][j] = j;    }    dist[0][0]=0;    for(int i =1;i<str1_len;i++)    {        for(int j= 1;j<str2_len;j++)        {            if(str1[i-1] ==str2[j-1])            {                dist[i][j]=dist[i-1][j-1];            }            else            {                dist[i][j] = minnum(dist[i-1][j-1],dist[i][j-1],dist[i-1][j])+1;            }        }    }    d = dist[str1_len-1][str2_len-1];    for(int i=0;i<str1_len;i++)        delete [] dist[i];    delete [] dist;    return d;}int main(){    string a = "qweoa";    string b = "qhiogl";    int dis=0;    dis = caculate_distance(a,b);    cout<<"distance is : "<<dis<<endl;    return 0;}


4

8*8的棋盘上面放着64个不同价值的礼物,每个小的棋盘上面放置一个礼物(礼物的价值大于0),一个人初始位置在棋盘的左上角,每次他只能向下或向右移动一步,并拿走对应棋盘上的礼物,结束位置在棋盘的右下角,请设计一个算法使其能够获得最大价值的礼物。

#include <iostream>#include<string.h>#include<stdio.h>#include<stdlib.h>#include<fstream>using namespace std;int  maxnum(int a,int b){    return (a>b)?a:b;}int caculateMaxValue(int **temp){    int n = 9;    int **dpvalue;    dpvalue = new int*[n];    for(int i=0;i<n;i++)        dpvalue[i] = new int[n];   int maxvalue = 0;   dpvalue[0][0] = 0;   for(int i=1;i<n;i++)   {       dpvalue[i][0] = dpvalue[i-1][0]+temp[i][0];   }   for(int j=1;j<n;j++)   {       dpvalue[0][j]=dpvalue[0][j-1]+temp[0][j-1];   }   for(int i=1;i<n;i++)   {       for(int j=1;j<n;j++)       {           dpvalue[i][j] = maxnum(dpvalue[i-1][j],dpvalue[i][j-1]) + temp[i][j];       }   }   maxvalue = dpvalue[n-1][n-1];   return maxvalue;}int main(){    ifstream in("/home/shq/testcode/test.txt");    int n=9;    int **temp;    temp = new int*[n];    for(int i=0;i<n;i++)        temp[i] = new int[n];    for(int i = 0;i<n;i++)    {        for(int j= 0;j<n;j++)        {            in>>temp[i][j];        }    }   cout<<caculateMaxValue(temp)<<endl;
  for(int i=0;i<n;i++)
     delete []temp[i];
   delete[] temp;    return 0;}


5求一个数组中最大连续子序列

#include <iostream>#include<stdio.h>#include<stdlib.h>using namespace std;void max_sub(int arr[],int l){    int *sum= new int(l);    int *len = new int(l);    sum[0] = arr[0];    len[0] = 0;    for(int i=0;i<l;i++)    {        if(sum[i-1]+arr[i]>arr[i])        {            sum[i] = sum[i-1]+arr[i];            len[i] = len[i-1]+1;        }        else        {            sum[i] = arr[i];            len[i] = 0;        }    }    int index = 0;    for(int i=0;i<l;i++)    {        if(sum[i]>sum[index])            index = i;        else if(sum[i] == sum[index] && len[i] < len[index])            index = i;    }    cout<<"the max sum is :"<<sum[index]<<endl;    cout<<"the sub string is"<<index - len[index]<<"-"<<index<<endl;
  delete []sum;
  delete []len;}int main(){   int a[] = {};   int l = sizeof(a)/sizeof(int);   max_sub(a,l);    return 0;}


6捞鱼问题:

20个桶,每个桶中有10条鱼,用网从每个桶中抓鱼,每次可以抓住的条数随机,每个桶只能抓一次,问一共抓到180条的排列有多少种。

分析:看看这个问题的对偶问题,抓取了180条鱼之后,20个桶中剩下了20条鱼,不同的抓取的方法就对应着这些鱼在20个桶中不同的分布,于是问题转化为将20条鱼分到20个桶中有多少中不同的分类方法(这个问题当然也等价于180条鱼分到20个桶中有多少种不同的方法)。

dp[i][j]:前i个桶放j条鱼的方法共分为11种情况:前i-1个桶放j-k(0<=k<=10)条鱼的方法总和。我们可以得到状态方程:f(i,j) = sum{ f(i-1,j-k), 0<=k<=10}

#include <iostream>using namespace std;void distributefish(int bucket,int fish){    int **dp;    dp = new int *[bucket+1];    for(int i=0;i<=bucket;i++)        dp[i] = new int[fish+1];    for(int i=1;i<=10;i++)    {        dp[1][i] = 1;    }    for(int i=2;i<=bucket;i++)    {        for(int j=0;j<=fish;j++)        {            for(int k=0;k<=10&&j-k>=0;k++)            {                dp[i][j]+=dp[i-1][j-k];            }        }    }    cout<<"distribute fish means is :"<<dp[bucket][fish];    for(int i=0;i<=bucket;i++)        delete []dp[i];    delete []dp;    return ;}int main(){    int bucket = 2;    int fish = 10;    distributefish(bucket,fish);    return 0;}


7.使用动态规矩计算0-1背包问题

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

基本思路

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

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

这个方程非常重要,基本上所有跟背包相关的问题的方程都是由它衍生出来的。所以有必要将它详细解释一下:“将前i件物品放入容量为j的背包中”这个子问题,若只考虑第i件物品的策略(放或不放),那么就可以转化为一个只牵扯前i-1件物品的问题。如果不放第i件物品,那么问题就转化为“前i-1件物品放入容量为v的背包中”;如果放第i件物品,那么问题就转化为“前i-1件物品放入剩下的容量为j-w[i]的背包中”,此时能获得的最大价值就是val[i-1][j-w[i]]再加上通过放入第i件物品获得的价值v[i]。

注意val[i][v]有意义当且仅当存在一个前i件物品的子集,其费用总和为v。所以按照这个方程递推完毕后,最终的答案并不一定是f[N][V],而是f[N][0..V]的最大值。如果将状态的定义中的“恰”字去掉,在转移方程中就要再加入一项val[i][v-1],这样就可以保证f[N][V]就是最后的答案。


以下用两种方法解决0-1背包问题:

方法1采用从上到下,从左到右的方式计算

方法2采用从下到上,从左到右的方式计算

bag01.h

#ifndef _BAG01_H_#define _BAG01_H_#include<stdio.h>#include<iostream>#include<stdlib.h>using namespace std;int maxval(int x,int y);int minv(int x,int y);void bag1(int n,int v[],int w[],int c);void bag2(int n,int v[],int w[],int c);#endif // _BAG01_H_

bag01.cpp

#include"bag01.h"extern int val[10][100];int maxval(int x,int y){    return (x>y)?x:y;}int minv(int x,int y){    return (x<y)?x:y;}void bag1(int n,int v[],int w[],int c){    for(int i=0;i<=n;i++) val[i][0]=0;    for(int j=0;j<=c;j++) val[0][j]=0;    for(int i=1;i<=n;i++)    {        for(int j=1;j<=c;j++)        {            if(j<w[i]) val[i][j] = val[i-1][j];            else            {             val[i][j] = maxval(val[i-1][j],val[i-1][j-w[i]]+v[i]);            }        }    }    return ;}void bag2(int n, int v[], int w[], int c){    int jmax;    jmax = minv(w[n],c);    for(int j = 0; j < jmax; j++) val[n][j] = 0;//j<当前背包容量或者当前物品重量时,val[n][j]=0;    for(int j = w[n]; j <= c; j++) val[n][j] = v[n];//当前背包容量可以装得下时,val[n][j]=v[n];    for(int i=n-1;i>=1;i--)    {        jmax = minv(w[i],c);        for(int j = 0; j < jmax; j++)        {            val[i][j] = val[i+1][j];        }        for(int j = jmax; j <= c; j++)        {            val[i][j]=maxval(val[i+1][j],val[i+1][j-w[i]]+v[i]);//当前背包容量装得下,但是要判断其价值是否最大,确定到底装不装        }    }    return;}

main.cpp

#include"bag01.h"int val[10][100]={0};int main(){    int weight[6]={0,2,2,6,5,4};    int value[6]={0,6,3,5,4,6};    int c=10;    int maxv=0;    bag1(5,value,weight,c);    //bag2(5,value,weight,c);    for(int i=0;i<=10;i++)    {        for(int j=0;j<=100;j++)            maxv = maxval(val[i][j],maxv);    }    printf("%d\n",maxv);    for(int i=0;i<6;i++)    {          for(int j=0;j<11;j++)          {            printf("%2d ",val[i][j]);           }        cout<<endl;    }    return 0;}


8. 多重背包问题

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

基本算法

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

#ifndef _MULTIBAG_H_#define _MULTIBAG_H_#include<iostream>#include<stdlib.h>#include<stdio.h>extern int val[100][100];using namespace std;int maxval(int x, int y);int minval(int x, int y);void multipleBag(int n, int c, int w[], int v[], int num[]);#endif // _MULTIBAG_H_

#include"multiBag.h"int maxval(int x, int y){    return x>y?x:y;}int minval(int x, int y){    return x<y?x:y;}void multipleBag(int n, int c, int w[], int v[], int num[]){    for(int i=0; i<=n; i++) val[i][0]=0;    for(int i=0; i<=c; i++) val[0][i]=0;    for(int i=1; i<=n; i++)    {        for(int j=w[i]; j<=c; j++)        {            int min_k=minval(num[i],c/w[i]);            for(int k=0; k<min_k; k++)            {                val[i][j]=maxval(val[i][j],val[i-1][j-k*w[i]]+k*v[i]);            }        }    }    return;}

#include"multiBag.h"int val[100][100];int main(){    const int n = 3;//物品个数    const int c = 8;//背包容量    int w[n + 1] = {0,1,2,2};    int v[n + 1] = {0,6,10,20};    int num[n + 1] = {0,10,5,2};    int max_val=0;    multipleBag(n,c,w,v,num);    for(int i=0; i<=n; i++)    {        for(int j=0; j<=c; j++)            max_val = maxval(max_val,val[i][j]);    }    cout<<max_val<<endl;    return 0;}

9.完全背包问题

题目

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

基本思路

这个问题非常类似于01背包问题,所不同的是每种物品有无限件。也就是从每种物品的角度考虑,与它相关的策略已并非取或不取两种,而是有取0件、取1件、取2件……等很多种。如果仍然按照解01背包时的思路,令f[i][c]表示前i种物品恰放入一个容量为c的背包的最大权值。仍然可以按照每种物品不同的策略写出状态转移方程,像这样:f[i][c]=max{f[i-1][c-k*w[i]]+k*v[i]|0<=k*w[i]<=c}。这跟01背包问题一样有O(n*c)个状态需要求解,但求解每个状态的时间则不是常数了,求解状态f[i][c]的时间是O(c/w[i]),总的复杂度是超过O(cn)的。

将01背包问题的基本思路加以改进,得到了这样一个清晰的方法。这说明01背包问题的方程的确是很重要,可以推及其它类型的背包问题。但我们还是试图改进这个复杂度。


#ifndef _MULTIBAG_H_#define _MULTIBAG_H_#include<iostream>#include<stdlib.h>#include<stdio.h>extern int val[100];using namespace std;int maxval(int x, int y);int minval(int x, int y);void completeBag(int n, int c, int w[], int v[]);#endif // _MULTIBAG_H_

#include"completeBag.h"int maxval(int x, int y){    return x>y?x:y;}int minval(int x, int y){    return x<y?x:y;}void completeBag(int n, int c, int w[], int v[]){    for(int i=1; i<=n; i++)    {        for(int j=w[i]; j<=c; j++)        {            val[j]=maxval(val[j],val[j-w[i]]+v[i]);        }    }    return;}

#include"completeBag.h"int val[100];int main(){    const int n = 4;//物品个数    const int c = 10;//背包容量    int w[n + 1] = {0,2,3,4,7};    int v[n + 1] = {0,1,3,5,9};    int max_val=0;    completeBag(n,c,w,v);    for(int i=0; i<=n; i++)    {        for(int j=0; j<=c; j++)        {            max_val = maxval(max_val,val[j]);            cout<<val[j]<<"\t";        }            cout<<endl;    }    cout<<max_val<<endl;    return 0;}







0 0
原创粉丝点击