完全背包问题

69 阅读3分钟

3. 完全背包问题 - AcWing题库 画图

image.png 解析

f[i][j]f[i][j] 表示所有满足从前i个物品选k个物品,总体积超过j的所有选法中价值最大的选法

因为每件物品可以无限选,所以每件物品可以选择多少件的情况如下:

  • 从前i个物品中选0个
  • 从前i个物品中选1个
  • 从i个物品中选2个
  • 从i个物品中选k-1个
  • 从i个物品中选k个

一.第i件物品选0个,也就说第i件物品不选,那么选的就是从前i-1个物品中选,且体积不能超过j的选法。用公式表示为 :f[i1][j] f[i-1][j]

二.从第i个物品中选k个,求最大价值,同样划分为子问题:

1.因为所有选法,第i个物品都选了k个,我们可以不包含第k件物品

2.因为去了第k个,相当于不包括第i个物品,所以应该是从前i-1个物品中选,且体积不能超过jkv[i]j-k*v[i] (要给第k个物品留空间)

3.再把k个物品i加回来,即+kw[i]+k*w[i]

4.所以从第i个物品中选K个选法是f[i1][jkv[i]+kw[i]] f[i-1][j-k*v[i]+k*w[i]]

ps: 从前i个物品中选0个的选法包含在从前i个物品中选k个的选法中因为从前i个物品中选0个的选法实际上就是从前i个物品中选k个,其中k为0的选法

code

#include<iostream>
using namespace std;
const int N=1010;
int dp[N][N];
int w[N],v[N];
int main()
{
    int n,m;
    cin>>n>>m;
    
    for(int i=0;i<n;++i)
    {
        cin>>v[i]>>w[i];
        
    }
    for(int i=1;i<=n;++i)
      for(int j=0;j<=m;++j)
          for(int k=0;k*v[i]<=j;++k)//前i个物品选k个,k个物品最大体积不能超过j,因为j是总体积,这里还要给第i个物品留空间
               dp[i][j]=max(dp[i][j],dp[i-1][j-k*v[i]+k*w[i]]);

    cout<<dp[n][m]<<endl;
    
    return 0;
}

image.png

之所以超时是因为有三层for循环,而数据范围如下:

1000行,每行最大1000,一行三层循环就已经1e9了。 image.png

优化

f[i , j ] = max( f[i-1,j] , f[i-1,j-v]+w ,  f[i-1,j-2*v]+2*w , f[i-1,j-3*v]+3*w , .....)
f[i , j-v]= max(            f[i-1,j-v]   ,  f[i-1,j-2*v] + w , f[i-1,j-3*v]+2*w , .....)
由上两式,可以发现
         f[i,j]的每一项都比 f[i,j-v]的每一项多一个w.
可得出如下递推关系: 
                        f[i][j]=max(f[i,j-v]+w , f[i-1][j]) 
#include<iostream>
using namespace std;
const int N=1010;
int dp[N][N];
int w[N],v[N];
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 = 0 ; j <=m ;j++)
    {
        dp[i][j] = dp[i-1][j];
        if(j-v[i]>=0)//前i个物品选k个的体积大于0
        dp[i][j]=max(dp[i][j],dp[i][j-v[i]]+w[i]);
    }
        cout<<dp[n][m]<<endl;
    
    return 0;
}