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

news2025/2/25 5:41:11

title: 动态规划-背包问题
date: 2023-05-12 13:01:31
categories:

  • Algorithm
  • 动态规划
    tags:
  • 动态规划

01背包问题

题目

题目链接:https://www.acwing.com/problem/content/description/2/

N N N 件物品和一个容量是 V V V 的背包。每件物品只能使用一次。

i i i 件物品的体积是 v i v_i vi,价值是 w i w_i wi

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式

第一行两个整数, N , V N,V NV,用空格隔开,分别表示物品数量和背包容积。

接下来有 N N N 行,每行两个整数 v i , w i v_i, w_i vi,wi,用空格隔开,分别表示第 i i i 件物品的体积和价值。

输出格式

输出一个整数,表示最大价值。

数据范围

0 < N , V ≤ 1000 0 \lt N, V \le 1000 0<N,V1000

0 < v i , w i ≤ 1000 0<v_i,w_i≤1000 0<vi,wi1000

输入样例

4 5
1 2
2 4
3 4
4 5

输出样例:

8

思路 O ( n 2 ) O(n^2) O(n2)

  • dp[i][j]表示前i个物品,背包容量为j下的最大价值

    • N件物品,需要进行N次决策,每一次对第i件物品进行决策
  • 当背包容量不够时j<v[i],前i个物品最大价值即为前i-1个物品的最大价值

    • dp[i][j]=dp[i-1][j]
  • 当背包容量够的时候,分两种情况,选与不选第i个物品,两种情况取Max

    • i:dp[i][j]=dp[i-1][j-v[i]]+w[i]
    • 不选i:dp[i][j]=dp[i-1][j]

代码

#include <bits/stdc++.h>

#define int long long
using namespace std;

signed main() {
#ifndef ONLINE_JUDGE
    freopen("test.in", "r", stdin);
    freopen("test.out", "w", stdout);
#endif
    int n, m;
    cin >> n >> m;
    vector<int> v(n + 1), w(n + 1);
    vector<vector<int> > f(n + 1, vector<int>(m + 1));
    //所有从前i个物品中选,容量不超过j的最大价值
    for (int i = 1; i <= n; i++) cin >> v[i] >> w[i];
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j <= m; j++) {
            f[i][j]=f[i-1][j];
            if (j>=v[i]) f[i][j]= max(f[i][j],f[i-1][j-v[i]]+w[i]);
        }
    }
    cout<<f[n][m]<<endl;
    return 0;
}

思路-空间优化为 O ( n ) O(n) O(n)

维优化:

  • 将状态dp[i][j]变成一维dp[j],只需要做一个等价变形
  • dp[j]的含义:N件物品,背包容量为j下的最大价值
    • 注意枚举背包容量的时候必须从最大容量m开始枚举
  • 为什么需要逆序枚举:
    • 二维情况下,dp[i][j]是由上一轮i-1的状态得来的,dp[i][j]dp[i-1][j]互不影响
    • 一维情况下,如果还是用正序枚举,那么dp[较小体积]更新得到dp[较大体积],则有可能本该用第i-1轮的状态,却是用的第i轮的状态

如果j是顺序循环,那么f[j-w[i]]会先于f[j]更新,也就是说,会用新值f[j-w[i]]去更新f[j],所以出错

  • 状态转移方程:dp[j]=max(dp[j],dp[j-v[i]+w[i])
  • 我们的代码中决策到第 i件物品(循环到第i轮),f[j]就是前i轮已经决策的物品且背包容量 j 下的最大价值。

具体过程如下:

  当还未进入循环时:
  f[0] = 0;  f[1] = 0;  f[2] = 0;  f[3] = 0;  f[4] = 0;  
  f[5] = 0;  f[6] = 0;  f[7] = 0;  f[8] = 0;  f[9] = 0; f[10] = 0;
  当进入循环 i == 1 时:w[i] = 5; v[i] = 4;
  j = 10:f[10] = max(f[10], f[6] + 5);max(0, 5) = 5; 即f[10] = 5;
  j = 9 :f[9] = max(f[9], f[5] + 5);max(0, 5) = 5; 即f[9] = 5;
  j = 8 :f[8] = max(f[8], f[4] + 5);max(0, 5) = 5; 即f[8] = 5;
  j = 7 :f[7] = max(f[7], f[3] + 5);max(0, 5) = 5; 即f[7] = 5;
  j = 6 :f[6] = max(f[6], f[2] + 5);max(0, 5) = 5; 即f[6] = 5;
  j = 5 :f[5] = max(f[5], f[1] + 5);max(0, 5) = 5; 即f[5] = 5;
  j = 4 :f[6] = max(f[4], f[0] + 5);max(0, 5) = 5; 即f[4] = 5;
  当进入循环 i == 2 时:w[i] = 6; v[i] = 5; 
  j = 10:f[10] = max(f[10], f[5] + 6);max(5, 11) = 11; 即f[10] = 11;
  j = 9 :f[9] = max(f[9], f[4] + 6);max(5, 11) = 5; 即f[9] = 11;
  j = 8 :f[8] = max(f[8], f[3] + 6);max(5, 6) = 6; 即f[8] = 6;
  j = 7 :f[7] = max(f[7], f[2] + 6);max(5, 6) = 6; 即f[7] = 6;
  j = 6 :f[6] = max(f[6], f[1] + 6);max(5, 6) = 6; 即f[6] = 6;
  j = 5 :f[5] = max(f[5], f[0] + 6);max(5, 6) = 6; 即f[5] = 6;
  当进入循环 i == 3: w[i] = 7; v[i] = 6; 
  j = 10:f[10] = max(f[10], f[4] + 7);max(11, 12) = 12; 即f[10] = 12;
  j = 9 :f[9] = max(f[9], f[3] + 6);max(11, 6) = 11; 即f[9] = 11;
  j = 8 :f[8] = max(f[8], f[2] + 6);max(6, 6) = 6; 即f[8] = 6;
  j = 7 :f[7] = max(f[7], f[1] + 6);max(6, 6) = 6; 即f[7] = 6;
  j = 6 :f[6] = max(f[6], f[0] + 6);max(6, 6) = 6; 即f[6] = 6;
数据来源:https://www.acwing.com/solution/content/30250/

代码

#include <bits/stdc++.h>


using namespace std;

int main() {
#ifndef ONLINE_JUDGE
    freopen("test.in", "r", stdin);
    freopen("test.out", "w", stdout);
#endif
    int n, m;
    cin >> n >> m;
    vector<int> v(n + 1), w(n + 1);
    vector<int> f(m + 1);
    //所有从前i个物品中选,容量不超过j的最大价值
    for (int i = 1; i <= n; i++) cin >> v[i] >> w[i];
    for (int i = 1; i <= n; i++) {
        for (int j = m; j >= v[i]; j--) {
            f[j] = max(f[j], f[j - v[i]] + w[i]);
        }
    }
    cout << f[m] << endl;
    return 0;
}

完全背包问题

题目

链接:https://www.acwing.com/problem/content/3/

N N N 种物品和一个容量是 V V V 的背包,每种物品都有无限件可用。

i i i 种物品的体积是 v i v_i vi,价值是 w i w_i wi

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式

第一行两个整数, N , V N,V NV,用空格隔开,分别表示物品种数和背包容积。

接下来有 N N N 行,每行两个整数 v i , w i v_i, w_i vi,wi,用空格隔开,分别表示第 i i i 种物品的体积和价值。

输出格式

输出一个整数,表示最大价值。

数据范围

0 < N , V ≤ 1000 0 \lt N, V \le 1000 0<N,V1000
0 < v i , w i ≤ 1000 0 \lt v_i, w_i \le 1000 0<vi,wi1000

输入样例

4 5
1 2
2 4
3 4
4 5

输出样例:

10

思路(朴素做法)

f[i][j]表示将前i件物品放进容量为j的背包的最大价值

  • 如果当前背包容量j<w[i],不能放入,则f[i][j]=f[i-1][j]
  • 如果当前背包容量j>=w[i],可以放入:
    • 若第i件物品不放入,则f[i][j]=f[i-1][j]
    • 若第i件物品放入,则f[i][j]=f[i][j-w[i]]+c[i]

状态转移方程:

  • f[i][j]=f[i-1][j],(j<w[i])
  • f[i][j]=max(f[i-1][j],f[i][j-w[i]]+v[i]),(j>=w[i])

代码

#include <bits/stdc++.h>

#define int long long
using namespace std;


signed main() {
#ifndef ONLINE_JUDGE
    freopen("test.in", "r", stdin);
    freopen("test.out", "w", stdout);
#endif
    int n, m;
    cin >> n >> m;
    vector<int> v(n + 1), w(n + 1);
    vector<vector<int> > f(n + 1, vector<int>(m + 1));
    for (int i = 1; i <= n; i++) cin >> v[i] >> w[i];
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                if (j < v[i]) f[i][j] = f[i - 1][j];
                else f[i][j] = max(f[i - 1][j], f[i][j - v[i]] + w[i]);
            }
        }
    cout << f[n][m] << endl;

    return 0;
}

优化(一维空间)

可以将空间优化掉一维,优化之后从前往后枚举即可,区别于01背包:

  • f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);//01背包

  • f[i][j] = max(f[i][j],f[i][j-v[i]]+w[i]);//完全背包问题

代码

#include <bits/stdc++.h>

#define int long long
using namespace std;


signed main() {
#ifndef ONLINE_JUDGE
    freopen("test.in", "r", stdin);
    freopen("test.out", "w", stdout);
#endif
    int n, m;
    cin >> n >> m;
    vector<int> v(n + 1), w(n + 1);
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
    vector<int> f(m + 1);
    for (int i = 1; i <= n; i++) {
        for (int j = v[i]; j <= m; j++) {
            f[j] = max(f[j], f[j - v[i]] + w[i]);
        }
    }
    cout << f[m];

    return 0;
}

多重背包问题

题目

https://www.acwing.com/problem/content/description/4/

N N N 种物品和一个容量是 V V V 的背包。

i i i 种物品最多有 s i s_i si 件,每件体积是 v i v_i vi,价值是 w i w_i wi

求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。

输入格式

第一行两个整数, N , V N,V NV,用空格隔开,分别表示物品种数和背包容积。

接下来有 N N N 行,每行三个整数 v i , w i , s i v_i, w_i, s_i vi,wi,si,用空格隔开,分别表示第 i i i 种物品的体积、价值和数量。

输出格式

输出一个整数,表示最大价值。

数据范围

见不同思路

输入样例

4 5
1 2 3
2 4 1
3 4 3
4 5 2

输出样例:

10

思路(朴素 O ( n ∗ m ∗ ∑ s i ) O(n*m*\sum{s_i}) O(nmsi)

数据范围为:

0 < N , V ≤ 100 0 \lt N, V \le 100 0<N,V100
0 < v i , w i , s i ≤ 100 0 \lt v_i, w_i, s_i \le 100 0<vi,wi,si100

dp[i][j]表示从前i个物品中选,总体积不超过j的最大价值

根据第i个物品选多少个进行划分,假设选k个,则dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]*k]+w[i]*k)

代码

#include <bits/stdc++.h>

#define int long long
using namespace std;


signed main() {
#ifndef ONLINE_JUDGE
    freopen("test.in", "r", stdin);
    freopen("test.out", "w", stdout);
#endif
    int n, m;
    cin >> n >> m;
    vector<int> v(n + 1), w(n + 1), s(n + 1);
    vector<vector<int> > f(n + 1, vector<int>(m + 1));
    for (int i = 1; i <= n; i++) cin >> v[i] >> w[i] >> s[i];
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            for (int k = 0; k <= s[i] && k * v[i] <= j; k++) {
                f[i][j] = max(f[i][j], f[i - 1][j - v[i] * k] + w[i] * k);
            }
        }
    }
    cout << f[n][m];
    return 0;
}

思路(二进制 O ( n ∗ m ∗ ∑ l o g s i ) O(n*m*\sum{log{s_i}}) O(nmlogsi))

  • 二进制优化,举例说明,假设有11个商品

    • 正常背包的思路下,我们要求出含这组商品的最优解,我们要枚举12次(枚举装0,1,2…12个)。
    • 现在用二进制优化,把这11个商品分别打包成含商品个数为1个,2个,4个,4个(分别对应0001,0010,0100,0100)的四个”新的商品 “,
    • 这样将问题转化为01背包问题,对于每个商品,我们都只枚举一次,那么我们只需要枚举四次 ,就可以找出这含组商品的最优解。 这样就大大减少了枚举次数。

二进制拆分思想:将第i种物品拆分为若干件物品,每件物品的体积和价值乘以一个拆分系数( 1 , 2 1 , 2 2 , . . . 2 k − 1 , s i − 2 k + 1 1,2^1,2^2,...2^{k-1},s_i-2^k+1 1,21,22,...2k1,si2k+1)

代码

#include <bits/stdc++.h>

#define int long long
using namespace std;


signed main() {
#ifndef ONLINE_JUDGE
    freopen("test.in", "r", stdin);
    freopen("test.out", "w", stdout);
#endif
    int n, m;
    cin >> n >> m;
    vector<int> a, b;
    for (int i = 1; i <= n; i++) {
        int v, w, s;
        cin >> v >> w >> s;
        for (int j = 1; j <= s; j <<= 1) { //二进制拆分
            a.push_back(j * v);
            b.push_back(j * w);
            s -= j;
        }
        if (s){ //还剩下物品
            a.push_back(s*v);
            b.push_back(s*w);
        }
    }
    //01背包
    vector<int> f(m+1);
    for(int i=0;i<a.size();i++){
        for(int j=m;j>=a[i];j--){
            f[j]= max(f[j],f[j-a[i]]+b[i]);
        }
    }
    cout<<f[m];
    return 0;
}

思路(单调队列 O ( n ∗ m ) O(n*m) O(nm))

规律1:

f数组是按类进行更新的,可以把f[0...m]按体积v的余数拆分为v个类。

  • f[0],f[v],f[2v],...f[kv]
  • f[1],f[1+v],...f[1+kv];
  • f[j],f[j+v],...f[j+kv];

其中jv的余数, 0 < = j < = v − 1 0<=j<=v-1 0<=j<=v1,例如

  • f[0],f[2],f[4],f[6],f[8]
  • f[1],f[3],f[5],f[7],f[9]

规律2:

f[j]是由前面不超过数量s的同类值传递推得到的,这就相当于从前面宽度为s的窗口中挑选最大值来更新当前的值,因此,我们可以使用单调队列来维护窗口的最大值,从而将更新f[j]的次数降低为1次,这时候需要顺序更新f值,需要增加一个备份数组。

改造后如下顺序循环

  int n, m;
  cin >> n >> m;
  vector<int> v(n + 1), w(n + 1), s(n + 1);
  vector<int> f(m + 1), g(m + 1);
  for (int i = 1; i <= n; i++) {
      std::memcpy(g, f, sizeof f);
      for (int j = 1; j <= m; j++) {
          for (int k = 1; k <= s[i] && v[i] * s[i] <= j; k++)
              f[j]= max(g[j],g[j-k*v[i]]+k*w[i]);
      }
  }

在这里插入图片描述

每次去使用队头更新当前的最大值,但是注意队头不一定就是整个队列中的最大值,队头+还可以放入物品的价值是整个队列中最大的

  • h表示队头,t表示队尾,变量j表示类 j ∈ [ 0 , v ) j\in[0,v) j[0,v),k表示背包容量
  • 维护的窗口为[k-sv,k-v]
  • 当前还可以放入物品的个数为(k-q[h]/v)
  • 如果用g[k]比用g[q[t]]更新f[x]获得更大价值,可以有g[k]+(x-k)/v*w>=g[q[t]]+(x-q[t])/v*w,即g[k]>=g[q[t]]+(k-q[t])/v*w,那么就将队尾出队,表明g[k]g[q[t]]更有价值

代码

#include <bits/stdc++.h>

#define int long long
using namespace std;

const int N = 2e4 + 10;
int f[N], g[N], q[N];//q数组存下标


signed main() {
#ifndef ONLINE_JUDGE
    freopen("test.in", "r", stdin);
    freopen("test.out", "w", stdout);
#endif
    int n, m, v, w, s;
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        std::memcpy(g, f, sizeof f);
        cin >> v >> w >> s;
        for (int j = 0; j < v; j++) {
            int h = 0, t = -1;//h为头,t为尾
            for (int k = j; k <= m; k += v) {
                //q[h]不在窗口[k-s*v,k-v]中,队头出队
                if (h <= t && q[h] < k - s * v) h++;
                //使用队头最大值来更新f (k-q[h])/v是还可以放进物品的个数
                if (h <= t) f[k] = max(g[k], g[q[h]] + (k - q[h]) / v * w);
                //当前值比队尾更有价值,队尾出队
                while (h <= t && g[k] >= g[q[t]] + (k - q[t]) / v * w) t--;
                //下标入队,便于队头出队
                q[++t] = k;
            }
        }
    }
    cout<<f[m]<<endl;
    return 0;
}

混合背包问题

题目

链接:https://www.acwing.com/problem/content/7/

N N N 种物品和一个容量是 V V V 的背包。

物品一共有三类:

  • 第一类物品只能用1次(01背包);
  • 第二类物品可以用无限次(完全背包);
  • 第三类物品最多只能用 s i s_i si 次(多重背包);

每种体积是 v i v_i vi,价值是 w i w_i wi

求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。

输入格式

第一行两个整数, N , V N,V NV,用空格隔开,分别表示物品种数和背包容积。

接下来有 N N N 行,每行三个整数 v i , w i , s i v_i, w_i, s_i vi,wi,si,用空格隔开,分别表示第 i i i i i i 种物品的体积、价值和数量。

  • s i = − 1 s_i = -1 si=1 表示第 i i i 种物品只能用1次;
  • s i = 0 s_i = 0 si=0 表示第 i i i 种物品可以用无限次;
  • s i > 0 s_i >0 si>0 表示第 i i i 种物品可以使用 s i s_i si 次;

输出格式

输出一个整数,表示最大价值。

数据范围

0 < N , V ≤ 1000 0 \lt N, V \le 1000 0<N,V1000
0 < v i , w i ≤ 1000 0 \lt v_i, w_i \le 1000 0<vi,wi1000
− 1 ≤ s i ≤ 1000 -1 \le s_i \le 1000 1si1000

输入样例

4 5
1 2 -1
2 4 1
3 4 0
4 5 2

输出样例:

8

思路

参考上面的01背包问题,完全背包问题,多重背包问题

可以将多重背包问题用二进制优化的思路转换为01背包问题,这样就只需要做01背包和完全背包问题了,

  • f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);//01背包

  • f[i][j] = max(f[i][j],f[i][j-v[i]]+w[i]);//完全背包问题

优化为1维之后:f[j]=max(f[j],f[j-v]+w)

  • 01背包:从 m − > v m->v m>v枚举
  • 完全背包:从 v − > m v->m v>m枚举

代码

#include <bits/stdc++.h>

#define int long long
using namespace std;


signed main() {
#ifndef ONLINE_JUDGE
    freopen("test.in", "r", stdin);
    freopen("test.out", "w", stdout);
#endif
    int n, m;
    cin >> n >> m;
    vector<int> f(m + 1);
    for (int i = 1; i <= n; i++) {
        int v, w, s;
        cin >> v >> w >> s;
        if (s == 0) {//完全背包问题
            for (int j = v; j <= m; j++)
                f[j] = max(f[j], f[j - v] + w);
        } else {//多重背包问题和01背包问题
            if (s == -1) s = 1;
            for (int j = 1; j <= s; j <<= 1) {
                for (int k = m; k >= j*v; k--) {
                    f[k]= max(f[k],f[k-j*v]+j*w);
                }
                s-=j;
            }
            if (s){
                for(int k=m;k>=s*v;k--){
                    f[k]= max(f[k],f[k-s*v]+s*w);
                }
            }
        }
    }
    cout<<f[m]<<endl;

    return 0;
}

二维费用的背包问题

题目

链接:https://www.acwing.com/problem/content/8/

N N N 件物品和一个容量是 V V V 的背包,背包能承受的最大重量是 M M M

每件物品只能用一次。体积是 v i v_i vi,重量是 m i m_i mi,价值是 w i w_i wi

求解将哪些物品装入背包,可使物品总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。
输出最大价值。

输入格式

第一行三个整数, N , V , M N,V, M N,V,M,用空格隔开,分别表示物品件数、背包容积和背包可承受的最大重量。

接下来有 N N N 行,每行三个整数 v i , m i , w i v_i, m_i, w_i vi,mi,wi,用空格隔开,分别表示第 i i i 件物品的体积、重量和价值。

输出格式

输出一个整数,表示最大价值。

数据范围

0 < N ≤ 1000 0 \lt N \le 1000 0<N1000
0 < V , M ≤ 100 0 \lt V, M \le 100 0<V,M100
0 < v i , m i ≤ 100 0 \lt v_i, m_i \le 100 0<vi,mi100
0 < w i ≤ 1000 0 \lt w_i \le 1000 0<wi1000

输入样例

4 5 6
1 2 3
2 4 4
3 4 5
4 5 6

输出样例:

8

思路

动态规划:

  • 状态表示:f[i,j,k]所有从前i个物品中选,并且总体积不超过j,总重量不超过k的选法的最大价值
  • 状态计算:f[i,j,k]=max(f[i-1,j,k],f[i-1,j-v1[i],k-v2[i]]+w[i])

代码

#include <bits/stdc++.h>

#define int long long
using namespace std;

const int N = 110;
int f[N][N];


signed main() {
#ifndef ONLINE_JUDGE
    freopen("test.in", "r", stdin);
    freopen("test.out", "w", stdout);
#endif
    int n, m, w;//个数,体积,容量
    cin >> n >> m >> w;
    for (int i = 1; i <= n; i++) {
        int a, b, c;//体积,重量,价值
        cin >> a >> b >> c;
        for(int j=m;j>=a;j--){
            for(int k=w;k>=b;k--){
                f[j][k]= max(f[j][k],f[j-a][k-b]+c);
            }
        }
    }
    cout<<f[m][w];


    return 0;
}

分组背包问题

题目

链接:https://www.acwing.com/problem/content/9/

N N N 组物品和一个容量是 V V V 的背包。

每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 v i j v_{ij} vij,价值是 w i j w_{ij} wij,其中 i i i 是组号, j j j 是组内编号。

求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。

输出最大价值。

输入格式

第一行有两个整数 N , V N,V NV,用空格隔开,分别表示物品组数和背包容量。

接下来有 N N N 组数据:

  • 每组数据第一行有一个整数 S i S_i Si i i i个物品组的物品数量;
  • 每组数据接下来有 S i S_i Si 行,每行有两个整数 v i j , w i j v_{ij}, w_{ij} vij,wij,用空格隔开,分别表示第 i i i 个物品组的第 j j j 个物品的体积和价值;

输出格式

输出一个整数,表示最大价值。

数据范围

0 < N , V ≤ 100 0 \lt N, V \le 100 0<N,V100
0 < S i ≤ 100 0 \lt S_i \le 100 0<Si100
0 < v i j , w i j ≤ 100 0 \lt v_{ij}, w_{ij} \le 100 0<vij,wij100

输入样例

3 5
2
1 2
2 4
1
3 4
1
4 5

输出样例:

8

思路(朴素 O ( n ∗ v ∗ ∑ s i ) O(n*v*\sum{s_i}) O(nvsi))

状态表示:f[i][j]表示前i组物品,能放入容量为j的背包的最大价值

循环物品组,循环背包容量,对于第i组物品,容量为j的背包,有s+1种选法,取最大值

代码

#include <bits/stdc++.h>

#define int long long
using namespace std;

const int N = 110;
int v[N][N], w[N][N], s[N];
int f[N][N];
int n, m;

signed main() {
#ifndef ONLINE_JUDGE
    freopen("test.in", "r", stdin);
    freopen("test.out", "w", stdout);
#endif
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        cin >> s[i];
        for (int j = 1; j <= s[i]; j++) {
            cin >> v[i][j] >> w[i][j];
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            for(int k=0;k<=s[i];k++){
                if (j>=v[i][k]){
                    f[i][j]= max(f[i][j],f[i-1][j-v[i][k]]+w[i][k]);
                }
            }
        }
    }
    cout<<f[n][m]<<endl;

    return 0;
}

思路(空间优化1维)

  • dp[j]表示总体积为j时的最大价值
  • 必须先枚举体积,再枚举每一个物品。因为如果先枚举物品,那么你下一个物品不管从哪里转移都已经被上一个更新,
  • 同时,体积必须倒着枚举,否则一个物品将会被重复选择。

代码

#include <bits/stdc++.h>

#define int long long
using namespace std;

const int N = 110;
int v[N], w[N];
int f[N];
int n, m, s;

signed main() {
#ifndef ONLINE_JUDGE
    freopen("test.in", "r", stdin);
    freopen("test.out", "w", stdout);
#endif
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        cin >> s;
        for (int j = 1; j <= s; j++) cin >> v[j] >> w[j];
        for (int j = m; j >= 1; j--) {
            for(int k=0;k<=s;k++){
                if (j>=v[k]){
                    f[j]= max(f[j],f[j-v[k]]+w[k]);
                }
            }
        }
    }
    cout<<f[m];

    return 0;
}

有依赖的背包问题

题目

链接:https://www.acwing.com/problem/content/10/

N N N 个物品和一个容量是 V V V 的背包。

物品之间具有依赖关系,且依赖关系组成一棵树的形状。如果选择一个物品,则必须选择它的父节点。
如果选择物品5,则必须选择物品1和2。这是因为2是5的父节点,1是2的父节点。

每件物品的编号是 i i i,体积是 v i v_i vi,价值是 w i w_i wi,依赖的父节点编号是 p i p_i pi。物品的下标范围是 1 … N 1 … N 1N

求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。

输出最大价值。

输入格式

第一行有两个整数 N , V N,V NV,用空格隔开,分别表示物品个数和背包容量。

接下来有 N N N 行数据,每行数据表示一个物品。
i i i 行有三个整数 v i , w i , p i v_i, w_i, p_i vi,wi,pi,用空格隔开,分别表示物品的体积、价值和依赖的物品编号。
如果 p i = − 1 p_i = -1 pi=1,表示根节点。 数据保证所有物品构成一棵树。

输出格式

输出一个整数,表示最大价值。

数据范围

1 ≤ N , V ≤ 100 1 \le N, V \le 100 1N,V100
1 ≤ v i , w i ≤ 100 1 \le v_i, w_i\le 100 1vi,wi100

父节点编号范围:

  • 内部结点: 1 ≤ p i ≤ N 1 \le p_i \le N 1piN;
  • 根节点 p i = − 1 p_i = -1 pi=1;

输入样例

5 7
2 3 -1
2 2 1
3 5 1
4 7 2
3 6 2

输出样例:

11

思路

考虑以u为根节点的子树的所有物品,这颗子树的物品都最大价值应该是节点u和背包容量j的函数

f[u][j]表示选择以u为子树的物品,在体积和不超过j时的最大价值

每个节点有i个节点 s 1 , s 2 . . . s i s_1,s_2...s_i s1,s2...si可以选或者不选,可以将u的i个子节点看成i组物品,每组物品 s i s_i si按体积拆分,有 0 , 1 , 2 , . . . j − v [ i ] 0,1,2,...j-v[i] 0,1,2,...jv[i]种决策

按单位体积拆分是因为 s i s_i si的子孙有可能存在体积为1的物品,

拆分到 j − v [ u ] j-v[u] jv[u]是因为要留出 v [ u ] v[u] v[u]到空间装入节点u的物品

代码

#include <bits/stdc++.h>

#define int long long
using namespace std;

const int N = 110;
int n, m;
int v[N], w[N];
int h[N], e[N], ne[N], idx;
int f[N][N];

void add(int a, int b) {
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

void dfs(int u) {
    //f[u][i]表示以u为子树,体积不超过i的最大价值
    for (int i = v[u]; i <= m; i++) f[u][i] = w[u];
    for (int i = h[u]; ~i; i = ne[i]) {
        int to = e[i];
        dfs(to);
        for (int j = m; j >= v[u]; j--) {
            for (int k = 0; k <= j - v[u]; k++) {
                f[u][j] = max(f[u][j], f[u][j - k] + f[to][k]);
            }
        }
    }
}


signed main() {
#ifndef ONLINE_JUDGE
    freopen("test.in", "r", stdin);
    freopen("test.out", "w", stdout);
#endif
    cin >> n >> m;
    memset(h, -1, sizeof h);
    int root;
    for (int i = 1; i <= n; i++) {
        int p;//每个节点的父节点
        cin >> v[i] >> w[i] >> p;
        if (p == -1) root = i;
        else add(p, i);
    }
    dfs(root);
    cout << f[root][m];
    return 0;
}

背包问题求方案数

题目

链接:https://www.acwing.com/problem/content/11/

N N N 件物品和一个容量是 V V V 的背包。每件物品只能使用一次。

i i i i i i 件物品的体积是 v i v_i vi,价值是 w i w_i wi

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

输出 最优选法的方案数。注意答案可能很大,请输出答案模 1 0 9 + 7 10^9 + 7 109+7 的结果。

输入格式

第一行两个整数, N , V N,V NV,用空格隔开,分别表示物品数量和背包容积。

接下来有 N N N 行,每行两个整数 v i , w i v_i, w_i vi,wi,用空格隔开,分别表示第 i i i 件物品的体积和价值。

输出格式

输出一个整数,表示 方案数 1 0 9 + 7 10^9 + 7 109+7 的结果。

数据范围

0 < N , V ≤ 1000 0 \lt N, V \le 1000 0<N,V1000
0 < v i , w i ≤ 1000 0\lt v_i, w_i \le 1000 0<vi,wi1000

输入样例

4 5
1 2
2 4
3 4
4 6

输出样例:

2

思路

f[i]表示背包容量为i时能装入物品的最大价值

c[i]表示背包容量为i时最优选法的方案数

容量从j-v增加到j,只是增加了一件物品,方案数没有改变c[j]=c[j-v]

代码

#include <bits/stdc++.h>

#define int long long
using namespace std;

const int N = 1010, mod = 1e9 + 7;
int n, m;

signed main() {
#ifndef ONLINE_JUDGE
    freopen("test.in", "r", stdin);
    freopen("test.out", "w", stdout);
#endif
    cin >> n >> m;
    vector<int> f(m + 1), c(m + 1, 1);//不选也是一种方案
    for (int i = 1; i <= n; i++) {
        int v, w;
        cin >> v >> w;
        for (int j = m; j >= v; j--) {
            if (f[j - v] + w > f[j]) {//
                f[j] = f[j - v] + w;
                c[j] = c[j - v];//只是增加一个物品,方案数不变
            } else if (f[j - v] + w == f[j]) {//说明有新的选法
                c[j] = (c[j] + c[j - v]) % mod;
            }
        }
    }
    cout<<c[m]<<endl;


    return 0;
}

背包问题求具体方案

题目

链接:https://www.acwing.com/problem/content/12/

N N N 件物品和一个容量是 V V V 的背包。每件物品只能使用一次。

i i i i i i 件物品的体积是 v i v_i vi,价值是 w i w_i wi

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

输出 字典序最小的方案。这里的字典序是指:所选物品的编号所构成的序列。物品的编号范围是 1 … N 1 … N 1N

输入格式

第一行两个整数, N , V N,V NV,用空格隔开,分别表示物品数量和背包容积。

接下来有 N N N 行,每行两个整数 v i , w i v_i, w_i vi,wi,用空格隔开,分别表示第 i i i 件物品的体积和价值。

输出格式

输出一行,包含若干个用空格隔开的整数,表示最优解中所选物品的编号序列,且该编号序列的字典序最小。

物品编号范围是 1 … N 1 … N 1N

数据范围

0 < N , V ≤ 1000 0 \lt N, V \le 1000 0<N,V1000
0 < v i , w i ≤ 1000 0\lt v_i, w_i \le 1000 0<vi,wi1000

输入样例

4 5
1 2
2 4
3 4
4 6

输出样例:

1 4

思路

状态表示:f[i][j]表示从第i个物品到最后一个物品装入容量为j的背包的最优解

与之前的区别就是这里是倒着的

选第i个:f[i][j]=f[i+1][j]

不选:f[i][j]=f[i+1][j-v[i]]+w[i]

计算完状态后:f[1][m]就是最大价值,从f[1][m]开始搜索

对于f[i][j]得到的方式,确定是否选择第i个物品:

  • f[i][j]=f[i+1][j],不选第i个物品
  • f[i][j]=f[i+1][j-v[i]]+w[i],选第i个物品
  • f[i][j]=f[i+1][j]=f[i+1][j-v[i]]+w[i],都可以得到最优解,为了字典序最小,必须选。

代码

#include <bits/stdc++.h>

#define int long long
using namespace std;


signed main() {
#ifndef ONLINE_JUDGE
    freopen("test.in", "r", stdin);
    freopen("test.out", "w", stdout);
#endif
    int n, m;
    cin >> n >> m;
    vector<vector<int> > f(n + 2, vector<int>(m + 1));
    vector<int> v(n + 2), w(n + 2);
    for (int i = 1; i <= n; i++) cin >> v[i] >> w[i];

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

    int j = m;
    for (int i = 1; i <= n; i++) {
        if (j >= v[i] && f[i][j] == f[i + 1][j - v[i]] + w[i]) {
            cout << i << " ";
            j -= v[i];
        }
    }


    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/522104.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据分析05——往Pandas中导入数据

1、导入Excel&#xff1a; 注意这种方法可以导入xlsx和xls两种类型的数据读入的数据会以DataFrame的格式显示举例&#xff1a;df pd.read_excel(‘./data/demo_03.xlsx’)还可以导入excel文件中具体的某一个表格&#xff1a;pd.read_excel(‘./data/demo_03.xlsx’, sheet_na…

SD-MTSP:麻雀搜索算法SSA求解单仓库多旅行商问题(提供MATLAB代码,可以修改旅行商个数及起点)

一、单仓库多旅行商问题 多旅行商问题&#xff08;Multiple Traveling Salesman Problem, MTSP&#xff09;是著名的旅行商问题&#xff08;Traveling Salesman Problem, TSP&#xff09;的延伸&#xff0c;多旅行商问题定义为&#xff1a;给定一个&#x1d45b;座城市的城市集…

postgresql源码学习(55)—— 列中的NULL值是如何存储和判断的?

问题来自 《PostgreSQL面试题集锦》学习与回答_Hehuyi_In的博客-CSDN博客 第11题 一、 NULL值存储位置 在pg元组头数据中&#xff0c;有一个t_bits数组&#xff0c;用于存储空值位图。当元组中没有null值的时候&#xff0c;t_bits可以被认为是空的&#xff0c;当元组有null值的…

javaweb学习 html+css基础1

1、学习路线 2、web网站的开发模式 课程安排 web 前端 前端学习内容 html和css的帮助网站 HTML 系列教程 (w3school.com.cn) html快速入门 使用VSCode开发工具&#xff0c;输入 &#xff01;enter&#xff0c;即可出现骨架。 ctrl/ 可以将文字转换为注释 右击一个页面…

【JavaEE】计网之IP协议+以太网+DNS

博主简介&#xff1a;想进大厂的打工人博主主页&#xff1a;xyk:所属专栏: JavaEE初阶 网络层重点协议——IP协议&#xff0c;在复杂的网络环境中确定一个合适的路径~ 本篇文章我们来讲解什么是 IP协议及以太网&#xff0c;在IP协议中&#xff0c;有 地址管理 和 路由选择 两个…

C++中基类和派生类的构造函数与析构函数的调用顺序分析

基类为B&#xff0c;派生类为C&#xff0c;直接上代码以及运行结果。 目录 思路分析 代码一 运行结果一 代码二&#xff1a;B(i)改为b(i) 运行结果二 代码三&#xff1a;加上B(i) 运行结果三 代码四&#xff1a;删掉C类定义的B对象b&#xff0c;删除b(i) 运行结果四 思路…

Linux使用全应用

一、CentOS安装Docker Docker CE 支持 64 位版本 CentOS 7&#xff0c;并且要求内核版本不低于 3.10&#xff0c; CentOS 7 满足最低内核的要求&#xff0c;所以我们在CentOS 7安装Docker。 基础命令 搜索镜像&#xff1a;docker search mysql 下载镜像&#xff1a;docker p…

[架构之路-197]-《软考-系统分析师》- 关键技术 - 问题分析阶段重要的四个任务

目录 前言&#xff1a; 一、信息系统/软件产品的问题分析概述 二、信息系统/软件产品的问题/痛点分析四步骤 步骤1、问题领域分析&#xff1a;研究遇到问题的业务领域&#xff08;诉求&#xff09; 步骤2、 领域问题和机会分析&#xff1a;分析业务领域的问题、痛点、难点…

计算机网络学习 一 (计算机网络体系结构)

计算机网络 基本概念 计算机网络是一个将分散的,具有独立功能的计算机系统. 简单来说,计算机网络就是一些互连的,自治的计算机系统的集合 分类 广义上:是一个资源共享的系统. 资源共享上: 1.目的–资源共享 2.组成单元–分布在不同地理位置的多台独立的"自治计算机"…

第十四届蓝桥杯青少组模拟赛Python真题 (2022年11月8日)

第十四届蓝桥杯青少组模拟赛Python真题 (2022年11月8日) 编程题 第 1 题 问答题 二进制位数 十进制整数2在十进制中是1位数&#xff0c;在二进制中对应10&#xff0c;是2位数。 十进制整数22在十进制中是2位数&#xff0c;在二进制中对应10110&#xff0c;是5位数。 请问十…

Linux高级---k8s之service服务

文章目录 一、service基本概念二、service类型三、service的使用1、实验环境准备2、ClusterIP类型的Service3、HeadLiness类型的Service4、NodePort类型的Service5、LoadBalancer类型的Service6、ExternalName类型的Service 一、service基本概念 在kubernetes中&#xff0c;pod…

【Java多线程编程】线程的六种状态

前言&#xff1a; 在我们进行多线程编程&#xff0c;脑海里会想到线程运行的状态到底是什么&#xff1f;因此我整理出这线程的状态这篇博文。线程的状态分为六种&#xff1a;新建状态&#xff08;NEW&#xff09;、就绪状态&#xff08;RUNNABLE&#xff09;、阻塞状态&#xf…

1-Linux环境安装JDK

Linux环境安装JDK 准备&#xff1a; ① Linux 环境 本文中Linux环境为 CentOS Linux 7 可使用以下命令查询 linux 系统版本&#xff1a; hostnamectl② 准备JDK包 进入官网 https://www.oracle.com/java/technologies/downloads/#java17下载对应jdk包 此处使用以前下载的旧…

Linux驱动编程(驱动程序基石)(下)

一、中断的线程化处理 复杂、耗时的事情&#xff0c;尽量使用内核线程来处理。上节视频介绍的工作队列用起来挺简单&#xff0c;但是它有一个缺点&#xff1a;工作队列中有多个 work&#xff0c;前一个 work 没处理完会影响后面的 work。解决方法有很多种&#xff0c;比如干脆…

String类的学习笔记(下):字符串拼接以及StringBuilder和StringBuffer的学习

本文介绍了String类对字符串进行拼接的方法 和拼接字符串的效率分析 以及能对字符串内容进行修改的StringBuilder和StringBuffer类其常用方法和区别 , 最后介绍了两个字符串经典面试题 StringBuilder和StringBuffer的学习 一.String类概括二.StringBuilder和StringBuffer1.字符…

是未来的超级计算机还是只是一场炒作?

随着科技的飞速发展和创新&#xff0c;量子计算技术逐渐成为了人们关注的热点话题。量子计算作为一种前沿的计算方式&#xff0c;具有超强的运算能力和突破性的创新潜力&#xff0c;因此备受瞩目。然而&#xff0c;随着各大公司和机构纷纷加入到这一领域的竞争中&#xff0c;一…

超详细github配置(仔细看,看完不会,你怪我)

github的重要性&#xff1a; 网络时代的程序员必备。 github的作用&#xff1a; 版本管理多人协作开源共享 常用方案&#xff1a; gitTortoiseGitgithub [Tortoise&#xff0c;程序员常称其为小乌龟&#xff0c;小海龟] 安装配置步骤 1.注册 GitHub: Where the world bu…

服务(第二十二篇)主从复制和读写分离

主从复制原理&#xff1a; 首先主节点会开启二进制日志&#xff0c;从节点会开启中继日志&#xff0c;从节点会开启io线程检测主节点是否有更新&#xff0c;如果更新了就会向主节点请求二进制事件&#xff0c;主会开启dump线程发送二进制事件&#xff0c;然后保存在从节点的中…

假如面试官让你十分钟完成双向循环链表

&#x1f48c; 博客内容&#xff1a;假如面试官让你十分钟完成双向循环链表&#xff0c;多一秒都不行 &#x1f600; 作  者&#xff1a;陈大大陈 &#x1f680; 个人简介&#xff1a;一个正在努力学技术的准前端&#xff0c;专注基础和实战分享 &#xff0c;欢迎私信&…

大前端技能讲解:NodeJS、Npm、Es6、Webpack

文章目录 1. 基础概述2. Nodejs2.1 Nodejs 了解和快速入门2.2 Nodejs 实现 Httpserver 服务&#xff08;实现请求响应&#xff09;2.3 Nodejs 操作 MySQL 数据库 3. ES63.1 ES6 的概述3.2 ES6 的语法&#xff1a;let 和 const 命令3.3 ES6 的语法&#xff1a;模板字符串3.4 ES6…