zoukankan      html  css  js  c++  java
  • 由DAG到背包问题——记忆化搜索和递推两种解法

    一、问题描述

    物品无限的背包问题:有n种物品,每种均有无穷多个。第 i 种物品的体积为Vi,重量为Wi。选一些物品装到一个容量为 C 的背包中,求使得背包内物品总体积不超过C的前提下重量的最大值。1≤n≤100, 1≤Vi≤C≤10000, 1≤Wi≤1000000.

    二、解题思路

    我们可以先求体积恰好为 i 时的最大重量(设为d[i]),然后取d[i]中的最大值(i ≤ C)。与之前硬币问题,“面值恰好为S”就类似了。只不过加了新属性——重量,相当于把原来的无权图改成带权图,即把“+1”变成“+W[j]”。这样,问题就变成了求以C为起点、终点任意的,边权之和最大的路径。

    三、代码实现

    1、记忆化搜索

    之前纠结这种方法的时间复杂度,先给结果:O(maxn * maxc)。因为计算dp(s)时,如果dp[i]中i是从0-->C,

    则dp[i] = max(dp[i],dp[i - V[j]] + W[j]),dp[i - V[j]]已经计算出来且保存,相当于得到dp[i]没有花费时间。如果dp[i]中i是从C-->0,

    每次计算的都被保存且只计算一次,有几次小的递归,也相当于没有花费时间。

     1 #include<stdio.h>
     2 #include<iostream>
     3 #include<cstring>
     4 #include<algorithm>
     5 using namespace std;
     6 
     7 const int INF = 0x3f3f3f3f;
     8 const int maxn = 100 + 10;
     9 const int maxc = 10000 + 10;
    10 int n,V[maxn],W[maxn],C;
    11 int d[maxc];        //d[i]表示总体积恰好为i时的最大重量
    12 
    13 int dp(int s)
    14 {
    15     int& ans = d[s];
    16     if (ans != -1)  return ans;
    17     ans = - INF;
    18     for (int i = 0; i < n; i++)
    19     {
    20         if (s >= V[i])  ans = max(ans, dp(s - V[i]) + W[i]);
    21     }
    22     return ans;
    23 }
    24 void slove()
    25 {
    26     memset(d, -1, sizeof(d));
    27     d[0] = 0;
    28     int res = -1;
    29     for (int i = 0; i <= C; i++)
    30         res = max(res, dp(i));
    31     printf("%d
    ", res);
    32 }
    33 
    34 int main()
    35 {
    36     while (scanf("%d",&n) == 1 && n)
    37     {
    38         scanf("%d", &C);
    39         for (int i = 0; i < n; i++)
    40             scanf("%d%d", &V[i], &W[i]);
    41 
    42         slove();
    43     }
    44     return 0;
    45 }

    2、递推式

    这种写法时间复杂度十分显然,与记忆化搜索相同,都是O(maxn * maxc)。但必须注意循环的顺序,比如容量只能从0-->C,而不能反过来,前一种写法则没有循环的顺序要求。

     1 void slove()
     2 {
     3     fill(d, d + n, -INF);
     4     d[0] = 0;
     5     int res = -1;
     6     for (int i = 0; i <= C; i++)        //容量的循环顺序只能是从小到大
     7     {
     8         for (int j = 0; j < n; j++)
     9         {
    10             if(i >= V[j])  d[i] = max(d[i], d[i - V[j]] + W[j]);
    11         }
    12         res = max(res, d[i]);
    13     }
    14     printf("%d
    ", res);
    15 }

    3、两者比较

    在得到状态转移方程之后,还需要思考如何编写程序。尽管在很多情况下,记忆化搜索程序更直观、易懂,但在0-1背包中递推法更理想。因为已知状态转移方程后,递推法的难点是循环顺序,而有了“阶段”定义后,循环顺序变得十分显然。

  • 相关阅读:
    LeetCode91 Decode Ways
    LeetCode93 Restore IP Addresses
    LeetCode92 Reverse Linked List II
    LeetCode90 Subsets II
    LeetCode89 Gray Code
    最长公共子序列及其引申问题
    constexpr:编译期与运行期之间的神秘关键字
    I/O模型: 阻塞、非阻塞、I/O复用、同步、异步
    LeetCode86 Partition List
    maven 安装 过程
  • 原文地址:https://www.cnblogs.com/lfri/p/9446353.html
Copyright © 2011-2022 走看看