多重背包问题

多重背包问题

问题

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

\(i\) 种物品最多有 \(s_i\) 件,每件体积是 \(v_i\),价值是 \(w_i\)

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

详见4. 多重背包问题 I - AcWing题库

题解

DP

相较于完全背包只需考虑背包容量,多重背包既要考虑背包容量,又要考虑背包容量足够时的物品数量:

#include<iostream>
#include<cmath>
using namespace std;
int n ,m ,v[105] ,w[105] ,s[105] ,f[105][105] ;

int main()
{
    cin >> n >> m;
    
    for(int i = 1 ;i <= n ;i ++)
    {
        cin >> v[i] >> w[i] >> s[i];
        for(int j = 0 ;j <= m ;j ++)
            for(int k = 0 ;k <= s[i] ;k ++)
                if(k * v[i] <= j) f[i][j] = max(f[i][j] ,f[i - 1][j - k * v[i]] + k * w[i]);
    }
    
    cout << f[n][m] << endl;
    return 0;
}

相较于完全背包问题中的无穷多个物品,多重背包问题中的有限物品数可将其转化为\(01\)背包问题。

#include<iostream>
#include<cmath>
using namespace std;
int n ,m ,a ,b ,s ,v[10005] ,w[10005] ,f[105] ,t ;

int main()
{
    cin >> n >> m;
    
    while(n --)
    {
        cin >> a >> b >> s;
        while(s --)
        {
            v[++ t] = a;
            w[t] = b;
        }
    }
    
    for(int i = 1 ;i <= t ;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;
}

甚至不用另外的数组存储\(v_i\)以及\(w_i\)

#include<iostream>
#include<cmath>
using namespace std;
int dp[1005], n, m, v, w, s;

int main()
{
    cin >> n >> m ;
    while(n --)
    {
        cin >> v >> w >> s;
        while(s --)
        {
            for(int j = m ;j >= v ;j --)
                dp[j] = max(dp[j] ,dp[j - v] + w);
        }
    }
    
    cout << dp[m] << endl;
    return 0;
}

当然以上方式无法处理大量数据。

二进制优化

取第\(i\)种物品,共有\(s_i\)个,其中必定存在:

\[s_i=1+2+4+8+16+32+64+128+L+k \]

其中\(k\)为二进制后余数。

可以注意到:

\[1+2=3\quad 001_{(2)}+010_{(2)}=011_{(2)}\\ 1+4=5\quad 001_{(2)}+100_{(2)}=101_{(2)}\\ 2+4=6\quad 010_{(2)}+100_{(2)}=110_{(2)}\\ 1+2+4=7\quad 001_{(2)}+010_{(2)}+100_{(2)}=111_{(2)}\\ \]

显然可以将每组物品再次分组,再以\(01\)背包的方式解决,十进制数使用二进制数以运算:

#include<iostream>
#include<cmath>
using namespace std;
int n, m, a, b, s, v[100005], w[100005], f[100005];

int main()
{
    cin >> n >> m;
    
    int t = 1;
    while(n --)
    {
        cin >> a >> b >> s;
        
        int k = 1 ;
        while(s >= k)
        {
            v[t] = k * a;
            w[t ++] = k * b;
            s -= k;
            k *= 2;
        }
        
        if(s > 0)
        {
            v[t] = s * a;
            w[t ++] = s * b;
        }
    }
    
    for(int i = 1 ;i <= t ;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;
}

也可将输入与规划相结合:

#include<iostream>
#include<cmath>
using namespace std;
int n, m, v, w, s, f[10000];

int main()
{
    cin >> n >> m;
    while(n --)
    {
        cin >> v >> w >> s;
        for(int k = 1 ;k <= s ;k *= 2)
        {
            for(int j =m ;j >= v * k ;j --)
                f[j] = max(f[j] , f[j - v * k] + w * k);
            s -= k;
        }

        if(s)
            for(int j = m ;j >= v * s ;j --)
                f[j] = max(f[j] , f[j - v * s] + w * s);
    }
    
    cout << f[m] << endl;
    return 0;
}
posted @ 2024-11-03 12:32  DusXin  阅读(190)  评论(0)    收藏  举报