动态规划----背包问题

《最初始的01背包问题》

 

 让我们用暴力搜索试一试:

int dfs(int i,int j){}

这个函数的作用是:返回在前i个物品中选,体积不超过j的能得到的最大价值;

1 int dfs(int i,int j)
2 {
3     int res;
4     if (i==0)res=0;
5     if (j<w[i]) res=dfs(i-1,j);
6     else res=max(dfs(i-1,j),dfs(i-1,j-w[i])+v[i]);
7     return res; 
8 }

会发现可能有重复计算,比如:dfs(1,5)可能会计算好几遍

优化:

定义一个数组:dp[][];

int dfs(int i, int j)
{
    if (dp[i][j]!=-1) return dp[i][j];
    int res;
    if (i == 0)
        res = 0;
    if (j < w[i])
        res = dfs(i - 1, j);
    else
        res = max(dfs(i - 1, j), dfs(i - 1, j - w[i]) + v[i]);
    return dp[i][j]=res;
}

然后可以将dp[][]用非递归的形式写出

for (int i = 1; i <= n; i++)
{
    for (int j = 0; j <= m; j++)
    {
        if (j < w[i])
            dp[i][j] = dp[i - 1][j];
        else
            dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);
    }
}

初始化:
根据dp的定义来:dp[i][j]在前i件物品中选择总体积不大于j的最大价值
一般在背包问题中初始化只考虑再第0件物品的情况
即要考虑dp[0][0~m]的初始值
根据定义:dp[0][0]在前0件物品中选择总体积不大于0的最大价值肯定为0
dp[0][j]在前0件物品中选择总体积不大于j的最大价值肯定为0

《滚动数组优化》

1 for (int i=1;i<=n;i++)
2 {
3     for (int j=m;j>=w[i];j--)
4         dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
5 }

为啥不是for (int j=m;j>=0;j--) ,因为在j<w[i]时,是直接dp[i][j]=dp[i-1][j],这里改成一维后,dp[j]=dp[j]也没必要了

《背包问题求方案数》

 

 如果说上面的01背包问题给出限制是不大于j

那么这里是恰好为j

即:从前i个物品中选,体积恰好为j的方案数

由题意:dp[0][0]=1,什么都不选为1种方案;dp[0][j]=0,从前0个物品中选,体积恰好为j的方案数,根本不可能由这种方案,即dp[0][j]=0;

而且dp[i][j]=dp[i-1][j]+dp[i-1][j-w[i]];

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int M = 10010;
int n, m, dp[110][M];
int main()
{
    cin >> n >> m;
    int num;
    dp[0][0] = 1;
    for (int i = 1; i <= n; i++)
    {
     //这样写就不用开一个数组来记录数了 cin
>> num; for (int j = 0; j <= m; j++) { dp[i][j] += dp[i - 1][j]; if (j >= num) dp[i][j] += dp[i - 1][j - num]; } } cout << dp[n][m]; return 0; }

 《背包问题求具体方案数》

 

 这里以01背包为例:

  求具体方案数问题其实对应着求一个最短路问题

 

对于每一个状态如何判断其是从那个状态转移过来的?以dp[n][m]为例;

if dp[n][m]==dp[n-1][m] 说明dp[n][m]这个状态是从dp[n-1][m]转移过来的

if dp[n][m]==dp[n-1][m-w[i]]+v[i] 说明dp[n][m]这个状态是从dp[n-1][m-w[i]] 转移过来的

 

但是这里是按照字典序来输出的,上述的判断方式是从n->1进行判断

但应该从1开始判断到n

以物品1为案例:

  当物品1可有可无时:一定要选,可以保证字典序最小

  当物品1一定要选时:选

  当物品1一定不能选时:不选


即让状态转移为:dp[i][j]=max(dp[i][j],dp[i+1][j-w[i]]+v[i]);

变成如下:

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstring>
 4 using namespace std;
 5 const int N = 1010;
 6 int v[N], w[N], dp[N][N];
 7 int main()
 8 {
 9     int n, m;
10     cin >> n >> m;
11     for (int i = 1; i <= n; i++)
12         cin >> w[i] >> v[i];
13     // dp[i][j]的含义是从后第i个物品开始选,总体积不超过j,一直选道最后一件物品的最大价值;
14     // 明显dp[n+1][j]=0;
15     for (int i = n; i >= 1; i--)
16     {
17         for (int j = 0; j <= m; j++)
18         {
19             dp[i][j] = dp[i + 1][j];
20             if (j >= w[i])
21                 dp[i][j] = max(dp[i][j], dp[i + 1][j - w[i]] + v[i]);
22         }
23     }
    //开始寻找
24 int j = m; 25 for (int i = 1; i <= n; i++) 26 { 27 if (j >= w[i] && dp[i][j] == dp[i + 1][j - w[i]] + v[i]) 28 { 29 cout << i << " "; 30 j -= w[i]; 31 } 32 } 33 return 0; 34 }

 

posted @ 2022-07-28 18:27  次林梦叶  阅读(45)  评论(0)    收藏  举报