一、题意

给一个V(V<=10000)和一个n(n<=20),V代表磁带的最大长度,n表示n首曲子,然后给出每首曲子的长度,现在要求你选一些曲子放入磁带中,使得磁带的利用率最高,输出最终磁带中存放的曲子序列,并输出总长度。

二、解析

换了个皮也得认出这是一个01背包问题。翻译过来就是:
往体积为V的背包里放物品,n个物品的体积给出,在本题中体积也是价值,然后尽可能使背包里物品的总价值最大。
直接套用01背包问题的转移方程:dp[i][j] = max(dp[i + 1][j], dp[i + 1][j - v[i]] + w[i])
在本题中体积就是价值,所以即为 dp[i][j] = max(dp[i + 1][j], dp[i + 1][j - a[i]] + a[i])
dp[i][j]表示从第i个物品开始的之后的物品放入体积为j的背包中的最大价值

打印路径我还是比较喜欢递归打印的方法。见代码。

三、代码

#include <iostream>
#include <cmath>
using namespace std;
const int maxn = 20 + 5, maxv = 10000 + 5;
int V, n, a[maxn], dp[maxn][maxv];

void print_ans(int i, int j) {
    if(i == n) return;
    if(dp[i][j] == dp[i + 1][j - a[i]] + a[i]) {
        cout << a[i] << " ";
        print_ans(i + 1, j - a[i]);
    }
    else print_ans(i + 1, j);
}

int main() {
    while(cin >> V >> n) {
        for(int i = 0; i < n; i ++) cin >> a[i];

        for(int j = 0; j <= V; j ++) dp[n][j] = 0;
        for(int i = n - 1; i >= 0; i --) {
            for(int j = 0; j <= V; j ++) {
                dp[i][j] = dp[i + 1][j];  // 不选
                if(j >= a[i] && dp[i + 1][j - a[i]] + a[i] > dp[i][j]) {  // 选
                    dp[i][j] = dp[i + 1][j - a[i]] + a[i];
                }
            }
        }
        print_ans(0, V);
        cout << "sum:" << dp[0][V] << endl;
    }
}

四、归纳

  • 01背包问题:即将n个物品放入容量为V的背包中, 每个物品只有一个,要么选要么不选。
    • 转移方程:dp[i][j] = max(dp[i + 1][j], dp[i + 1][j - v[i]] + w[i])
    • dp[i][j]表示从第i个物品开始的之后的物品放入剩余体积为 j 的背包中的最大价值