01背包问题

01背包问题

n个重量和价值分别为wivi的物品。从这些物品中挑选出总重量不超过W的物品,求所有挑选方案中价值总和的最大值。

1 <= n <= 100

1 <= wi, vi <= 100

1 <= w <= 10000

int n, W;
int w[MAX_n], v[MAX_n];

//从第i个物品开始挑选总重小于j的部分
int rec(int i, int j)
{
    int res;
    if (i == n) {
        //已经没有剩余物品了
        res = 0;
    }
    else if (j < w[i]) {
        //无法挑选这个物品
        res = rec(i + 1, j);
    }
    else {
        res = max(rec(i + 1, j), rec(i + 1, j - w[i]) + v[i]);
    }
    return res;
}

这种方法的搜索深度为n,且每一层搜索都需要两次分支,最坏需要O(2^n)的时间。

该算法中许多参数被重复计算,我们可以将第一次的时间结果记录下来,省略重复计算。



int dp[MAX_N+1][MAX_N+1];

memset(dp, -1, sizeof(dp));

int rec(int i, int j)
{
    if (dp[i][j] >= 0) {
        //已经计算过的话直接使用之间的结果
        return dp[i][j];
    }
    int res;
    if (i == n) {
        res = 0;
    }
    else if (j < w[i]) {
        rec(i + 1, j);
    }
    else {
        res = max(rec(i + 1, j), rec(i + 1, j - w[i]) + v[i]);
    }
    //将结果记录在数组中
    return dp[i][j] = res;
}

对于同样的参数,只会在第一次被调用时执行递归部分,第二次之后都会直接返回。

参数组合不过nW种,而函数内只调用2次递归,所以只需要O(nW)的复杂度就能解决问题。



dp[i][j]为根据rec的定义,从第i个物品开始挑选总重小于j时,总价值的最大值,于是有如下递推式:

dp[n][j] = 0。

j < w[i]时,dp[i][j] = dp[i+1][j]。

其他情况下,dp[i][j] = max(dp[i+1][j], dp[i+1][j-w[i]]+v[i])。

int dp[MAX_N+1][MAX_N+1];

for (int i = n - 1; i >= 0; i--) {
    for (int j = 0; j <= W; 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]);
        }
    }
}
printf("%d\n", dp[0][W]);


dp[i+1][j]=0ii+1个物品中选出总重量不超过j的物品时总价值的最大值。

dp[0][j] = 0。

j < w[i]时,dp[i+1][j] = dp[i][j]。

其他情况下,dp[i+1][j] = max(dp[i][j], dp[i][j-w[i]] + v[i])。

for (int i = 0; i < n; i++) {
    for (int j = 0; j <= W; j++) {
        if (j < w[i]) {
            dp[i+1][j] = dp[i][j];
        }
        else {
            dp[i+1][j] = max(dp[i][j], dp[i][j-w[i]] + v[i]);
        }
    }
}
printf("%d\n", dp[n][W]);


此外,除了运用递推方式逐项求解之外,还可以把状态转移想象成从i个物品中选取总重不超过j时的状态i+1个物品中选取总重不超过j”i+1个物品中选取总重不超过j+w[i]时的状态的转移。

for (int i = 0; i < n; i++) {
    for (int j = 0; j <= w; j++) {
        dp[i+1][j] = max(dp[i+1][j], dp[i][j]);
        if (j + w[i] <= W) {
            dp[i+1][j+w[i]] = max(dp[i+1][j+w[i]], dp[i][j] + v[i]);
        }
    }
}
printf("%d\n", dp[n][W]);



当背包问题的限制条件很大时,还可以改变DP的对象。之前的方法中,我们用DP针对不同的重量限制计算最大的价值。

这次不妨用DP针对不同的价值计算最小的重量。

dp[i+1][j]=i个物品中挑选出价值总和为j时总重量的最小值(不存在时就是一个充分大的数值INF)

由于前0个物品中什么都挑选不了,所以初始值为:

dp[0][0] =0。

dp[0][j] = INF。

此外,前i个物品中挑选出价值总和为j时,一定有:

i个物品中挑选价值总和为j的部分。

i-1个物品中挑选出价值总和为j-v[i]的部分,然后再选中第i个物品。

所以得到dp[i+1][j] = min(dp[i][j], dp[i][j-v[i]]+w[i])。

最终的答案就对应于令dp[n][j] <= W的最大的j

int dp[MAX_N+1][MAX_N*MAX_V+1];

for (int i = 1; i < MAX_N * MAX_V + 1; i++)
dp[0][i] = INF;
dp[0][0] = 0;
for (int i = 0; i < n; i++) {
    for (int j = 0; j <= MAX_N * MAX_V; j++) {
        if (j < v[i]) {
            dp[i+1][j] = dp[i][j];
        }
        else {
            dp[i+1][j] = min(dp[i][j], dp[i][j-v[i]] + w[i]);
        }
    }
}
int res = 0;
for (int i = 0; i <= MAX_N*MAX_V; i++) {
    if (dp[n][i] <= W)
        res = i;
        }
printf("%d\n", res);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值