01背包

2023-08-30   


题意

NN件物品和一个容量为VV的背包,每件物品有各自的价值且只能被选择一次,要求在有限的背包容量下,装入的物品总价值最大。

分析

状态表示: F[i][j]F[i][j]:前ii个物品,背包容量jj下的最优解(最大价值)
状态计算:
当前背包容量不够j<v[i]j < v[i]时:F[i][j]=F[i1][j]F[i][j] = F[i - 1][j]
当前背包容量够,可分为选当前物品或不选当前物品
选:F[i][j]=F[i1][jv[i]]+w[i]F[i][j] = F[i - 1][j - v[i]] + w[i]
不选:F[i][j]=F[i1][j]F[i][j] = F[i - 1][j]
综上:F[i][j]=max(F[i1][j],F[i1][jv[i]]+w[i])F[i][j] = max(F[i - 1][j], F[i - 1][j - v[i]] + w[i])

代码

#include<bits/stdc++.h>

using namespace std;

const int MAXN = 1005;
int v[MAXN];    // 体积
int w[MAXN];    // 价值 
int f[MAXN][MAXN];  // f[i][j], j体积下前i个物品的最大价值 

int main() 
{
    int n, m;   
    cin >> n >> m;
    for(int i = 1; i <= n; i++) 
        cin >> v[i] >> w[i];

    for(int i = 1; i <= n; i++) 
        for(int j = 1; j <= m; j++)
        {
            //  当前背包容量装不进第i个物品,则价值等于前i-1个物品
            if(j < v[i]) 
                f[i][j] = f[i - 1][j];
            // 能装,需进行决策是否选择第i个物品
            else    
                f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);
        }           

    cout << f[n][m] << endl;

    return 0;
}

一维优化分析

状态表示: F[j]F[j]:背包容量jj下的最优解(最大价值)
状态计算: 状态计算基本同二维基本一致,只是枚举背包容量jj必须从mm倒序开始

为何倒序:

在二维中状态F[i][j]F[i][j]是由上一轮i1i - 1的状态得来的,F[i][j]F[i][j]F[i1][j]F[i - 1][j]是独立的。而优化到一维后,如果我们还是正序,则会先计算小容量的状态,小容量状态更新至ii轮,当计算后面大容量的状态是用的是小容量更新至ii轮的状态,显然就不符合状态定义了,故要倒序枚举。
更直白一点:jj从小到大,F[jv[i]]F[j-v[i]]中,由于jv[i]j-v[i]小于jjF[jv[i]]F[j-v[i]]已经在i这层循环被计算了,而我们想要的F[jv[i]]F[j-v[i]]应该是i1i-1层循环里面的,所以jj从大到小的话保证此时的F[jv[i]]F[j-v[i]]还未被计算,也就是第i1i-1层的数据。简单来说,一维情况正序更新状态F[j]F[j]需要用到前面计算的状态已经被「污染」,逆序则不会有这样的问题。

优化代码

for(int i = 1; i <= n; i++)
{
    for(int j = m; j >= v[i]; j--)  
        f[j] = max(f[j], f[j - v[i]] + w[i]);
} 

Q.E.D.