摘要:
0/1背包问题——动态规划模板
问题分析:
原题链接:P1048采药
孪生兄弟:完全背包问题
算法:
动态规划是将问题不断细分,将大问题划分为小问题,再逐步解决的方法。满足无后效性和最优子结构。动态规划应用最明显的特征就是每个子问题都仅仅求解一次,并在求解之后将结果保留。在下一次遇到同样的问题时可以直接得出答案。
0/1背包问题是指有m中物品,每一种物品都有其对应的价值。在背包体积不超过n的情况下,装走最大价值的物品,且每一种物品都最多选取一次。由于每一次选取物品后的最大价值,都和上一次选取后的总价值有关,因此该问题具有最优子结构,可用动态规划求解。
具体的状态转移方程见官方的网站的题目解答洛谷P1048采药——题目解答
这里只强调一点,就是代码中内层循环要逆着遍历,从大到小。如果是从小到大,即从1到n,则对应另一种动态规划问题——完全背包问题(一件物品可以重复多次选择)
另一个细节就是,本道题可以采用二维数组的方式进行,即ans[m][n],其中m是物品的件数,n是总时间限制。那么任何一个元素ans[i][j]则表示在前i件物品中用时长为j的时间获得的最大利润。
当然,更高效的是可以采用一维数组的方式进行求解。代码基本一样 ,但是意义没有二位数组明显。即ans[n]中的每一个元素ans[i]表示在时间为i的前提下,获得的最大利润,可以从n件物品中任意选取。
但是,无论是一维还是二维,其本质思想都是根据状态转移方程不断更新,得到最优值。
代码以及详细注释:
方法一:采用一维数组
#include <iostream>
#include <stdio.h>
#include <vector>
using namespace std;
#pragma warning(disable:4996)
class Solution {
public:
int n, m;
vector<pair<int, int>> w;
vector<long long int> ans;
void dynamic_programming() {
cin >> n >> m;
w.resize(m + 1);
ans.resize(n + 1,0);
for (int i = 1; i <= m; ++i)
cin >> w[i].first >> w[i].second;
for (int i = 1; i <= m; ++i)
{
for (int j = n; j >0; --j)
{
if (j >= w[i].first)
ans[j] = max(ans[j], ans[j - w[i].first] + w[i].second);
}
/*for(int i=1;i<=n;++i)
cout << ans[i] << " ";
cout << endl;*/
}
cout << ans[n]<<endl;
}
};
int main() {
//freopen("in.txt", "r", stdin);
Solution s;
s.dynamic_programming();
return 0;
}
方法二:二维数组求解:
#include <iostream>
#include <stdio.h>
#include <vector>
#pragma warning(disable:4996)
using namespace std;
class Solution {
public:
int n, m; //n是总时间 ,m是药品的件数
vector<pair<int,int>> w;
vector<vector<int>> ans; //利用二维网格搜索
void dynamic_programming() {
cin >> n >> m;
w.resize(m + 1);
ans.resize(m + 1, vector<int>(n+5, 0));
for (int i = 1; i <= m; ++i) {
int x, y;
cin >> x >> y;
w[i] = { x,y };
}
for (int i = 1; i <= m; ++i) {
for (int j = 1; j<=n; ++j) {
if (j >= w[i].first)
ans[i][j] = max(ans[i - 1][j], ans[i - 1][j - w[i].first] + w[i].second);
else
ans[i][j] = ans[i - 1][j];
}
}
cout << ans[m][n];
}
};
int main()
{
//freopen("in.txt", "r", stdin);
Solution s;
s.dynamic_programming();
return 0;
}