【ACWing】11. 背包问题求方案数

题目地址:

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

N N N件物品和一个容量是 V V V的背包。每件物品只能使用一次。第 i i i件物品的体积是 v i v_i vi,价值是 w i w_i wi。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最优选法的方案数。注意答案可能很大,请输出答案模 1 0 9 + 7 10^9+7 109+7的结果。

输入格式:
第一行两个整数, N N N V V V,用空格隔开,分别表示物品数量和背包容积。接下来有 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<N,V≤1000 0<N,V1000
0 < v i , w i ≤ 1000 0<v_i,w_i≤1000 0<vi,wi1000

思路是动态规划,具体方法是两个状态同时递推。设 f [ i ] [ j ] f[i][j] f[i][j]是只在前 i i i个物品里选,并且总体积恰好是 j j j的最大价值(这里取恰好的原因是让方案数的划分更加清晰,最后方案就可以按照总体积来划分)。那么 f f f的递推方程是: f [ i ] [ j ] = max ⁡ { f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − v i ] + w i } f[i][j]=\max\{f[i-1][j],f[i-1][j-v_i]+w_i\} f[i][j]=max{ f[i1][j],f[i1][jvi]+wi}初始条件 f [ 0 ] [ 0 ] = 0 , f [ 0 ] [ . > 0 ] = − ∞ f[0][0]=0,f[0][.>0]=-\infty f[0][0]=0,f[0][.>0]=,表示该状态转移过来的答案应该舍弃。当把 f [ N ] [ V ] f[N][V] f[N][V]递推出来以后,这个 f [ N ] [ V ] f[N][V] f[N][V]一定是从 f [ 0 ] [ 0 ] f[0][0] f[0][0]沿着某条路径走过来的,具体路径需要靠 f [ N ] [ V ] f[N][V] f[N][V]反向推回去,看 max ⁡ \max max函数取的是谁。题目要求解的,就是路径的总条数。设 g [ i ] [ j ] g[i][j] g[i][j]是上述从 ( 0 , 0 ) (0,0) (0,0)出发走到 ( i , j ) (i,j) (i,j)这个位置为止的路径总条数(即只取前 i i i个物品,总体积恰好是 j j j并且总价值是 f [ i ] [ j ] f[i][j] f[i][j]的方案数)。而 g g g的递推方程要根据 f f f的递推来做: g [ i ] [ j ] = { g [ i − 1 ] [ j ] , f [ i − 1 ] [ j ] > f [ i − 1 ] [ j − v i ] + w i g [ i − 1 ] [ j − v i ] , f [ i − 1 ] [ j − v i ] + w i > f [ i − 1 ] [ j ] g [ i − 1 ] [ j ] + g [ i − 1 ] [ j − v i ] , f [ i − 1 ] [ j ] = f [ i − 1 ] [ j − v i ] + w i g[i][j]=\begin{cases}g[i-1][j],f[i-1][j]> f[i-1][j-v_i]+w_i \\g[i-1][j-v_i], f[i-1][j-v_i]+w_i> f[i-1][j] \\g[i-1][j]+g[i-1][j-v_i], f[i-1][j]= f[i-1][j-v_i]+w_i\end{cases} g[i][j]=g[i1][j],f[i1][j]>f[i1][jvi]+wig[i1][jvi],f[i1][jvi]+wi>f[i1][j]g[i1][j]+g[i1][jvi],f[i1][j]=f[i1][jvi]+wi初始条件 g [ 0 ] [ 0 ] = 1 g[0][0]=1 g[0][0]=1,最后要返回的答案是: ∑ f [ n ] [ j ] = max ⁡ k f [ n ] [ k ] g [ n ] [ j ] \sum_{f[n][j]=\max_k f[n][k]} g[n][j] f[n][j]=maxkf[n][k]g[n][j]可以只开一维数组做空间优化,但是与 0 − 1 0-1 01背包类似,体积要从大到小循环。代码如下:

#include <iostream>
#include <cstring>
using namespace std;

const int N = 1010, mod = 1e9 + 7;
int n, m;
int f[N], g[N];

int main() {
    
    
    cin >> n >> m;
    
    memset(f, -0x3f, sizeof f);
    f[0] = 0;
    g[0] = 1;

    for (int i = 1; i <= n; i++) {
    
    
        int v, w;
        cin >> v >> w;
        for (int j = m; j >= v; j--) {
    
    
            int maxv = max(f[j], f[j - v] + w);
            int cnt = 0;
            // 看maxv是从哪里转移过来的,累加方案数
            if (maxv == f[j]) cnt += g[j];
            if (maxv == f[j - v] + w) cnt += g[j - v];
            g[j] = cnt % mod;
            f[j] = maxv;
        }
    }

    int res = 0;
    for (int i = 0; i <= m; i++) res = max(res, f[i]);

    int cnt = 0;
    for (int i = 0; i <= m; i++)
        if (f[i] == res)
            cnt = (cnt + g[i]) % mod;

    cout << cnt << endl;

    return 0;
}

时间复杂度 O ( N V ) O(NV) O(NV),空间 O ( V ) O(V) O(V)

当然如果直接采用 0 − 1 0-1 01背包的状态表示也是可以做的,设 f [ i ] [ j ] f[i][j] f[i][j]是只在前 i i i个物品里选,并且总体积不超过 j j j的最大价值。那么 f f f的递推方程是: f [ i ] [ j ] = max ⁡ { f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − v i ] + w i } f[i][j]=\max\{f[i-1][j],f[i-1][j-v_i]+w_i\} f[i][j]=max{ f[i1][j],f[i1][jvi]+wi}初始条件 f [ 0 ] [ j ] = 0 f[0][j]=0 f[0][j]=0。设 g [ i ] [ j ] g[i][j] g[i][j]是上述从 ( 0 , 0 ) (0,0) (0,0)出发走到 ( i , j ) (i,j) (i,j)这个位置为止的路径总条数(即只取前 i i i个物品,总体积不超过 j j j并且总价值是 f [ i ] [ j ] f[i][j] f[i][j]的方案数)。而 g g g的递推方程也是: g [ i ] [ j ] = { g [ i − 1 ] [ j ] , f [ i − 1 ] [ j ] > f [ i − 1 ] [ j − v i ] + w i g [ i − 1 ] [ j − v i ] , f [ i − 1 ] [ j − v i ] + w i > f [ i − 1 ] [ j ] g [ i − 1 ] [ j ] + g [ i − 1 ] [ j − v i ] , f [ i − 1 ] [ j ] = f [ i − 1 ] [ j − v i ] + w i g[i][j]=\begin{cases}g[i-1][j],f[i-1][j]> f[i-1][j-v_i]+w_i \\g[i-1][j-v_i], f[i-1][j-v_i]+w_i> f[i-1][j] \\g[i-1][j]+g[i-1][j-v_i], f[i-1][j]= f[i-1][j-v_i]+w_i\end{cases} g[i][j]=g[i1][j],f[i1][j]>f[i1][jvi]+wig[i1][jvi],f[i1][jvi]+wi>f[i1][j]g[i1][j]+g[i1][jvi],f[i1][j]=f[i1][jvi]+wi初始条件 g [ 0 ] [ j ] = 1 g[0][j]=1 g[0][j]=1,最后要返回的答案是: g [ n ] [ V ] g[n][V] g[n][V]。也可以只开一维数组做空间优化。代码如下:

#include <iostream>
#include <cstring>
using namespace std;

const int N = 1010, mod = 1e9 + 7;
int n, m;
int f[N], g[N];

int main() {
    
    
    cin >> n >> m;
    for (int i = 0; i <= m; i++) g[i] = 1;

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

    cout << g[m] << endl;

    return 0;
}

时空复杂度一样。

猜你喜欢

转载自blog.csdn.net/qq_46105170/article/details/114319229