POJ 3624 Charm Bracelet(0-1背包的动态规划!)

本文详细解析0-1背包问题及其动态规划解决方案,包括基本概念、状态转移方程、二维数组实现及空间优化技巧,并提供AC代码示例。

原题地址:

http://poj.org/problem?id=3624
0-1背包问题描述如下:
  有一个容量为M的背包,和N个物品。这些物品分别有两个属性,体积w和价值v,每种物品只有一个。要求用这个背包装下价值尽可能多的物品,求该最大价值,背包可以不被装满。
  因为最优解中,每个物品都有两种可能的情况,即在背包中或者不存在(背包中有0个该物品或者 1个),所以我们把这个问题称为0-1背包问题。

解题思路

0-1背包问题时动态规划里最基础也相对重要的题型,是《背包九讲》里的第一篇,优快云上有大神做了总结,我觉得讲的非常清楚,在这里引用一下。

0-1背包问题的四种写法

有一下几点是关键:

  • 核心状态转移方程:dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]] + v[i]);

    • dp[i, j]表示前i个物体面对剩余容量为j时背包的最大价值,w[i]代表物体i的重量,v[i]代表物体i的价值;如果第i个物体不放入背包(可能放不下),则背包的最大价值等于前i-1个物体面对容量v的最大价值;如果第i个物体选择放入,则背包的最大价值等于前i-1个物体面对容量j-w[i]的最大价值加上物体i的价值v[i]。

    • 一般采用二维数组(状态转移矩阵)dp[i][j]来记录各个子问题的最优状态,其中dp[i][j]表示前i个物体面对容量j背包的最大价值。此时的时间复杂度为O(N*M),空间复杂度也为O(N*V);附上trace函数打印最优解序列,即取了哪几个物品。

    • 当需要的空间太大时,比如这道题,N=3405不算大,但是M接近12885时这个空间需求就会非常大,导致Memory Limit Exceeded,所以必须要对空间进行优化,主要的思想就是:在状态转移的过程中(i-1 to i),当前状态(i)只与前一状态(i-1)时的解有关,那么之前存储的状态信息(i-2,i-3…)已经没有用途,可以舍弃来节约空间。

  • 空间优化版的状态转移方程:dp2[j] = max(dp2[j], dp2[j-w[i]] + v[i]);

    • 第i次循环结束后dp2[j]中所表示的就是使用二维数组时的dp[i][j],即前i个物体面对容量j时的最大价值。

    • 由于dp2[j]是由dp[i-1][j]和dp[i-1][j-w[i]]这两个状态得来的,对于前者,当第i次循环之前时,dp2[j]实际上就是dp[i-1][j],而对于后者,若j递增,则dp2[j-w[i]]代表的是更新后的dp[i][j-w[i]]而不是旧的的dp[i-1][j-w[i]],因此为了保护dp[i-1][j-w[i]]不被覆盖,必须让j逆序递减。(其实就是避免重复放入物品i,注意和完全背包的区别!)

    • 空间优化版本最后是求解不出来最优解序列的,但是能求出最优解,也就是最大价值

  • 背包问题最优解的两种问法:“恰好装满背包” “不需要背包装满”。区别这两种问法的实现方式在于矩阵初始化的不同。

    • 不需要装满:初始化时将dp[0][0..M]全部设为0,意味着最开始0也是合法的解,之后的过程中不装满也是合法的解。
    • 必须装满:初始化时除了dp[0][0]外,dp[0][1..M]全部设为-∞,意味着最开始没装满就是非法的解,之后的过程中有空余位置的dp[i][j]是非法的,值接近-∞。

AC代码

#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 3405;
const int maxm = 12885;

int w[maxn],v[maxn]; //物品重量w/价值v[1...n]
int dp[maxn][maxm];

void print_trace(int n, int m)
{
    cout << "最终的二维状态转移矩阵:" << endl;
    for (int i = 0; i <= n; ++i)
    {
        for (int j = 0; j <= m; ++j)
        {
            cout << dp[i][j] << ' ';
        }
        cout << endl;
    }
    int i = n, j = m;
    bool flag[maxn] = {0};
    while (dp[i][j] != 0)
    {
        if (dp[i][j] == dp[i-1][j-w[i]] + v[i]) //取了第i个物品
        {
            flag[i] = 1;
            j -= w[i];
        }
        --i;
    }
    cout << "取了第几个物品:(1..n) " << endl;
    for (int i = 1; i <= n; ++i)
        if(flag[i])
            cout << i << ": " << w[i] << ' ' << v[i] << endl;
}

///0-1背包动态规划解法:dp[i][j]表示前i件物品,背包剩余容量为j时,所取得的最大价值
void solve(int n, int m)
{
    for (int j = 0; j <= m; ++j)
        dp[0][j] = 0;
    for (int i = 1; i <= n; ++i)
    {
        for (int j = 0; j <= m; ++j)
        {
            if (w[i] > j) //第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]); //不加或加
        }
    }
    cout << "最大价值:";
    cout << dp[n][m] << endl;
    print_trace(n, m);
}


///0-1背包的空间优化解法:用一维数组dp2[j]表示dp[i][j]
void solve_better(int n, int m)
{
    int dp2[maxm];
    for (int j = 0; j <= m; ++j)
        dp2[j] = 0;
    for (int i = 1; i <= n; ++i)
        ///注意逆序的意义:由于dp[i][j]取决于dp[i-1][j]和dp[i-1][j-w[i]]
        ///对于后者,若j递增,则dp2[j-w[i]]代表的是更新后的dp[i][j-w[i]]而不是旧的的dp[i-1][j-w[i]]
        for (int j = m; j >= w[i]; --j) //仅当剩余重量能装下第i件物品才更新
            dp2[j] = max(dp2[j], dp2[j-w[i]] + v[i]);
    //cout << "最大价值:";
    cout << dp2[m] << endl;
    /*
    cout << "最终的一维状态转移矩阵:" << endl;
    for (int j = 0; j <= m; ++j)
        cout << dp2[j] << ' ';
    cout << endl;
    */
}

int main()
{
    ios::sync_with_stdio(false);
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; ++i)
        cin >> w[i] >> v[i];
    //cout << "\n-------空间未优化-------" << endl;
    //solve(n, m);
    //cout << "\n-------空间优化-------" << endl;
    solve_better(n, m);
    return 0;
}

算法复杂度:O(N*M) 空间复杂度:O(M)
耗时:266ms

测试结果

0-1背包测试

注意上面的二维矩阵和下面的一维向量是相同的 :)

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值