背包问题总结

基本思路

  • 第一维枚举物品、第二维枚举体积、第三维枚举决策

01背包

题意:有n个物品,背包总容量为v,第 i 个物品的体积为 v i v_i vi,价值为 w i w_i wi,求装入背包的最大价值
思路
{ 状 态 表 示 f [ i ] [ j ] = { 集 合 : 所 有 只 考 虑 前 i 种 物 品 且 总 体 积 不 超 过 j 的 方 案 的 集 合 属 性 : 取 其 中 价 值 最 大 的 方 案 状 态 计 算 : 找 最 后 一 个 不 同 点 , 对 第 i 个 物 品 取 或 者 不 取 \begin{cases} 状态表示f[i][j]= {\begin{cases} 集合:所有只考虑前i种物品且总体积不超过j的方案的集合\\ 属性:取其中价值最大的方案 \end{cases}}\\ 状态计算:找最后一个不同点,对第 i 个物品取或者不取 \end{cases} f[i][j]={iji

f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − w [ i ] ] + v [ i ] ) f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+v[i]) f[i][j]=max(f[i1][j],f[i1][jw[i]]+v[i])

这里第一维可以省略,只需要将dp方程等价转换。
在更新 f [ i ] [ j ] f[i][j] f[i][j] 的时候需要用到的 f [ i − 1 ] [ j − w [ i ] ] f[i-1][j-w[i]] f[i1][jw[i]],所以从右往左更新

未优化

#include <iostream>
using namespace std;
const int maxn=1010;
int n,m,c[maxn],v[maxn],dp[maxn][maxn];

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
        scanf("%d%d",&c[i],&v[i]);
    for(int i=1;i<=n;++i)
        for(int j=m;j>=0;--j)
        {
            dp[i][j]=dp[i-1][j];
            if(j>=c[i])
                dp[i][j]=max(dp[i-1][j],dp[i-1][j-c[i]]+v[i]);
        }
    printf("%d\n",dp[n][m]);
    return 0;
}

空间优化

#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=1010;
int n,m,w[maxn],v[maxn],f[maxn];

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
        scanf("%d%d",&w[i],&v[i]);
    for(int i=1;i<=n;++i)
        for(int j=m;j>=w[i];--j)
            f[j]=max(f[j],f[j-w[i]]+v[i]);
    printf("%d\n",f[m]);
    return 0;
}

完全背包问题

思路
{ 状 态 表 示 f [ i ] [ j ] = { 集 合 : 所 有 只 考 虑 前 i 种 物 品 且 总 体 积 不 超 过 j 的 方 案 的 集 合 属 性 : 取 其 中 价 值 最 大 的 方 案 状 态 计 算 : 找 最 后 一 个 不 同 点 , 对 第 i 个 物 品 取 0 个 , 1 个 , 2 个 \begin{cases} 状态表示f[i][j]= {\begin{cases} 集合:所有只考虑前i种物品且总体积不超过j的方案的集合\\ 属性:取其中价值最大的方案 \end{cases}}\\ 状态计算:找最后一个不同点,对第 i 个物品取0个,1个,2个 \end{cases} f[i][j]={iji012
0 0 0 f [ i − 1 ] [ j ] f[i-1][j] f[i1][j],取 1 1 1 f [ i − 1 ] [ j − w [ i ] ] + v [ i ] f[i-1][j-w[i]]+v[i] f[i1][jw[i]]+v[i]
f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − w [ i ] ] + v [ i ] , f [ i − 1 ] [ j − 2 w [ i ] ] + 2 v [ i ] … f [ i − 1 ] [ j − k w [ i ] ] + k v [ i ] f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+v[i],f[i-1][j-2w[i]]+2v[i]\\ \dots f[i-1][j-kw[i]]+kv[i] f[i][j]=max(f[i1][j],f[i1][jw[i]]+v[i],f[i1][j2w[i]]+2v[i]f[i1][jkw[i]]+kv[i]
这样的转移是可以化简的
f [ i ] [ j − w [ i ] ] = m a x ( f [ i − 1 ] [ j − w [ i ] ] , f [ i − 1 ] [ j − 2 w [ i ] ] + v [ i ] , f [ i − 1 ] [ j − 3 w [ i ] ] + 2 v [ i ] … f [ i − 1 ] [ j − ( k + 1 ) w [ i ] ] + k v [ i ] f[i][j-w[i]]=max(f[i-1][j-w[i]],f[i-1][j-2w[i]]+v[i],f[i-1][j-3w[i]]+2v[i]\\ \dots f[i-1][j-(k+1)w[i]]+kv[i] f[i][jw[i]]=max(f[i1][jw[i]],f[i1][j2w[i]]+v[i],f[i1][j3w[i]]+2v[i]f[i1][j(k+1)w[i]]+kv[i]
带入上式即可得

f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i ] [ j − w [ i ] ] + v [ i ] f[i][j]=max(f[i-1][j],f[i][j-w[i]]+v[i] f[i][j]=max(f[i1][j],f[i][jw[i]]+v[i]

不优化空间

#include <cstdio>
#include <iostream>
#include <cstring>
#define ll long long
using namespace std;
const int maxn=100+10,maxm=1e7+10;

int n,m;
int c[1010],v[1010];
int dp[1010][1010];

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;++i)
        cin>>c[i]>>v[i];
    for(int i=1;i<=n;++i)
    {
        for(int j=0;j<=m;++j)
        {
            dp[i][j]=dp[i-1][j];
            if(j>=c[i])
                dp[i][j]=max(dp[i][j],dp[i][j-c[i]]+v[i]);
        }
    }
    cout<<dp[n][m]<<"\n";
    return 0;
}

优化空间

#include <bits/stdc++.h>
using namespace std;
const int maxn=1010;
int n,m,w[maxn],v[maxn],f[maxn];

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
        scanf("%d%d",&w[i],&v[i]);
    for(int i=1;i<=n;++i)
        for(int j=w[i];j<=m;++j)
            f[j]=max(f[j],f[j-w[i]]+v[i]);
    printf("%d\n",f[m]);
    return 0;
}

多重背包问题

思路
{ 状 态 表 示 f [ i ] [ j ] = { 集 合 : 所 有 只 考 虑 前 i 种 物 品 且 总 体 积 不 超 过 j 的 方 案 的 集 合 属 性 : 取 其 中 价 值 最 大 的 方 案 状 态 计 算 : 找 最 后 一 个 不 同 点 , 对 第 i 个 物 品 有 [ 0 , s ] 种 取 法 \begin{cases} 状态表示f[i][j]= {\begin{cases} 集合:所有只考虑前i种物品且总体积不超过j的方案的集合\\ 属性:取其中价值最大的方案 \end{cases}}\\ 状态计算:找最后一个不同点,对第 i 个物品有 [0,s]种取法 \end{cases} f[i][j]={iji[0,s]
分析:其实也和01背包、完全背包没什么区别。只不过决策的次数变成了s次,物品、总体积、物品件数都在100以内

#include <bits/stdc++.h>
using namespace std;
const int maxn=110;
int n,m,w[maxn],v[maxn],s[maxn],dp[maxn][maxn];

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
        scanf("%d%d%d",&w[i],&v[i],&s[i]);
  
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            for(int k=0;k<=s[i]&&k*w[i]<=j;++k)
	            dp[i][j]=max(dp[i][j],dp[i-1][j-k*w[i]]+k*v[i]);
    printf("%d\n",dp[n][m]);    
    return 0;
}

多重背包问题—二进制优化

思路:状态设置: f [ i ] [ j ] f[i][j] f[i][j]表示所有前i个物品总体积不超过 j 的方案的集合,然后取一个价值最大的方案
采用了二进制优化,因为有 s s s 件物品,用二进制可以表示 s s s 中的任意一个数

#include <bits/stdc++.h>
using namespace std;

int n,m,w,v,s;
int f[2010];
vector<pair<int,int> > vec;

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
    {
        scanf("%d%d%d",&w,&v,&s);
        for(int k=1;k<=s;k*=2)
        {
            s-=k;
            vec.push_back({k*w,k*v});
        }
        if(s>0)
            vec.push_back({s*w,s*v});
    }
    for(auto i : vec)
        for(int j=m;j>=i.first;--j)
            f[j]=max(f[j],f[j-i.first]+i.second);
    printf("%d\n",f[m]);
    return 0;
}

多重背包问题—单调队列优化

题意:有n类物品,背包总容量为v,第i类物品,有 s i s_i si件体积为 v i v_i vi,价值为 w i w_i wi,求装入背包的最大价值
思路:状态设置还是 f [ i ] [ j ] f[i][j] f[i][j]表示前i个物品总体积不超过 j 的最大价值
分析:这里n范围在1000,v的范围在20000,s的范围在20000以内。如果还是采用二进制优化的话。时间复杂度是: 1000 × l o g 2 ( 20000 ) × 20000 1000\times log_2(20000)\times 20000 1000×log2(20000)×20000,大约是3e8,会超时。所以可以采用单调队列优化的方法
优化思路:我们发现每一个 f [ i ] [ j ] f[i][j] f[i][j]其实都是从 f [ i − 1 ] [ j − w ] 、 f [ i − 1 ] [ j − 2 ∗ w ] 、 … 、 f [ i − 1 ] [ j − s ∗ w ] f[i-1][j-w]、f[i-1][j-2*w]、\dots、f[i-1][j-s*w] f[i1][jw]f[i1][j2w]f[i1][jsw]转移过来的,因此我们可以根据余数 r 分组,每一组是: r 、 r + w 、 r + 2 w 、 … r、r+w、r+2w、\dots rr+wr+2w,维护一个最大值单调队列,每次先判断入队,然后缩减区间,最后更新 f [ j ] f[j] f[j]

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e4+5,maxm=1e5+5;
const int mod=1e9+7,inf=0x7f7f7f7f;

int n,m;
int w[maxn],v[maxn],s[maxn];
int q[maxn],h,t;
int f[maxn],g[maxn];

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i)
		scanf("%d%d%d",&w[i],&v[i],&s[i]);
	
	for(int i=1;i<=n;++i)
	{	
		memcpy(g,f,sizeof(f));
		for(int r=0;r<w[i];++r)
		{
			h=1,t=0;
			for(int j=r;j<=m;j+=w[i])
			{
				while(h<=t&&g[q[t]]+(j-q[t])/w[i]*v[i]<=g[j])
					t--;
				q[++t]=j;
				
				while(h<=t&&j-q[h]>s[i]*w[i])
					h++;
				f[j]=max(f[j],g[q[h]]+(j-q[h])/w[i]*v[i]);	
			}		
		}
	}
	printf("%d\n",f[m]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值