AcWing 3. 完全背包问题(模板)

题目链接:点击这里
在这里插入图片描述
完全背包问题描述:有 N N 种物品和一个容量为 V V 的背包,每种物品都有无限件可用。放入第 i i 种物品的费用是 C i C_i ,价值是 W i W_i 。求解:将哪些物品装入背包,可使这些物品的耗费的费用总和不超过背包容量,且价值总和最大。

【基本思路】

这个问题非常类似于 01 背包问题,所不同的是每种物品有无限件。也就是从每种物品的角度考虑,与它相关的策略已并非取或不取两种,而是有取 0 0 件、取 1 1 件、取 2 2 件……直至取 V / C i ⌊V /C_i⌋ 件等许多种。

如果仍然按照解 01 背包时的思路,令 F [ i , v ] F [i, v] 表示前 i i 种物品恰放入一个容量为 v v 的背包的最大权值。仍然可以按照每种物品不同的策略写出状态转移方程,像这样:
F [ i , v ] = m a x { F [ i 1 , v k C i ] + k W i 0 k C i v } F [i, v] = max\left\{F [i − 1, v − kC_i] + kW_i | 0 ≤ kC_i ≤ v\right\}

这跟 01 背包问题一样有 O ( V N ) O(V N) 个状态需要求解,但求解每个状态的时间已经不是常数了,求解状态 F [ i , v ] F [i, v] 的时间是 O ( v C i ) O(\frac{v}{C_i} ) ,总的复杂度可以认为是 O ( N V Σ V C i ) O(NV Σ\frac{V}{C_i} ) ,是比较大的。

这说明 01 背包问题的方程的确是很重要,可以推及其它类型的背包问题。但我们还是要试图改进这个复杂度。

【O(NV) 的算法】

#include<iostream>
#include<cstdio>
#include<algorithm>

using namespace std;

const int maxn = 1010;

int c[maxn], w[maxn];       //c是物品的体积,w是物品的价值

int dp[maxn];

int main()
{
    int N, V;
    scanf("%d%d", &N, &V);
    
    for(int i = 1; i <= N; ++i)
        scanf("%d%d", &c[i], &w[i]);
    
    for(int i = 1; i <= N; ++i)
    {
        for(int j = c[i]; j <= V; ++j)
        {
            dp[j] = max(dp[j], dp[j-c[i]]+w[i]);
        }
    }
    
    printf("%d", dp[V]);
    
    return 0;
}

会发现,这个代码与 01 背包问题的代码只有 v v 的循环次序不同而已。

为什么这个算法就可行呢?

首先想想为什么 01 背包中要按照 v v 递减的次序来循环。让 v v 递减是为了保证第 i i 次循环中的状态 F [ i , v ] F[i,v] 是由状态 F [ i 1 , v C i ] F[i − 1,v − C_i] 递推而来。换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入第 i i 件物品”这件策略时,依据的是一个绝无已经选入第 i i 件物品的子结果 F [ i 1 , v C i ] F[i − 1,v − C_i]

而现在完全背包的特点恰是每种物品可选无限件,所以在考虑“加选一件第 i i 种物品”这种策略时,却正需要一个可能已选入第 i i 种物品的子结果 F [ i , v C i ] F [i,v − C_i] ,所以就可以并且必须采用 v v 递增的顺序循环。这就是这个简单的程序为何成立的道理。

值得一提的是,上面的伪代码中两层 for 循环的次序可以颠倒。这个结论有可能会带来算法时间常数上的优化。

发布了748 篇原创文章 · 获赞 113 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/qq_42815188/article/details/104362434