背包问题
\(01\)背包
题目描述:有\(n\)件物品和一个容量为\(v\)的背包,每个物品放进背包需要占用\(S_i\)的空间,可以得到\(W_i\)的价值,放入一些物品使得价值最大,求最大值
分析:
状态设计:
- 明显的发现这个问题与\(v,S_i,W_i\)有关,于是可以将空间开出一维,价值用\(F\)数组的值表示,设\(F_{j}\)表示恰好占用\(j\)空间的最大价值
- 因为\(DP\)要从小问题得到大问题的解,由于物品排列的先后顺序不会影响答案,就可以加多一维\(i\),表示\(F_{i,j}\)前\(i\)个物品中,占用空间恰好为\(j\)的最大价值
转移方程:
- 假设当前我们要求\(F_{i,j}\),那么对于第\(i\)个物品,就有取和不取,于是就有$$F_{i,j}=\left{
\begin{aligned}
F_{i-1,j} (不取)(0\leqslant j \leqslant v)\
F_{i-1,j-S_i}+W_i (取)(s_i\leqslant j \leqslant v)\
\end{aligned}
\right.$$
- 于是枚举\(i,j\)即可解决问题
时空优化:
- 时间上复杂度为\(O(nv)\),无法优化
- 空间上复杂度为\(O(nv)\),发现\(F_{i,j}\)只与\(F_{i-1,j(0\leqslant j \leqslant v)}\)有关,于是可以把\(i\)用滚动数组滚掉。
\(F\)初始化:
- 明显的全部为\(0\)即可
代码实现(优化):
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
void read(int &sum)
{
sum=0;char last='w',ch=getchar();
while (ch<'0' || ch>'9') last=ch,ch=getchar();
while (ch>='0' && ch<='9') sum=sum*10+ch-'0',ch=getchar();
if (last=='-') sum=-sum;
}
int old[1010],now[1010];
struct node { int s,w; }a[1010];
int n,v;
int main()
{
// freopen("M.in","r",stdin);
// freopen("M.out","w",stdout);
read(n),read(v);
for (int i=1;i<=n;i++) read(a[i].s),read(a[i].w);
memset(old,0,sizeof(old));
memset(now,0,sizeof(now));
for (int i=1;i<=n;i++)
{
for (int j=0;j<=v;j++)
{
now[j]=old[j];
if (j>=a[i].s)
{
now[j]=max(now[j],old[j-a[i].s]+a[i].w);
}
}
for (int j=0;j<=v;j++)
old[j]=now[j];
}
int ans=0;
for (int i=0;i<=v;i++) ans=max(ans,old[i]);
printf("%d",ans);
// fclose(stdin);fclose(stdout);
return 0;
}
完全背包
题目描述:题目描述:有\(n\)种物品和一个容量为\(v\)的背包,每种物品放一个进背包需要占用\(S_i\)的空间,可以得到\(W_i\)的价值,放入一些物品使得价值最大,每种物品可以有无数个(空间大小要满足条件),求最大值
分析:
状态设计:
- 跟\(01\)背包同理,设\(F_{i,j}\)表示前\(i\)种物品恰好占用\(j\)空间物品的最大价值
转移方程:
- 假设当前选择到第\(i\)种物品,要选择\(k\)个,那么\(F_{i,j}\)可能由\(F_{i-1,j-k*S_i}\),或者直接继承\(F_{i-1,j}\)
时空优化:
- 原时间复杂度为\(O(nv\sum_{i=1}^n(\frac{v}{S_i}))\),空间为\(O(nv)\)
- 现在考虑优化,假设有两种状态分别为\(F_{i,j-S_i},F_{i,j}\),那我们在\(DP\),发现其实它们只有\(F_{i,j-S_i}\)的差别,于是考虑减少次数
- 发现\(F_{i,j-S_i}\)已经把除了\(F_{i,j-S_i}\)之外的所有\(F_{i,j}\)要考虑的状态都考虑了,所以我们发现可以把\(k\)去掉,从小到达枚举\(j\),每次只需转移\(F_{i,j}=F_{i,j-S_i}\)。注意要考虑不取的情况,即\(F_{i,j}=F_{i-1,j}\)
- 在加上一个滚动数组把\(i\)滚掉,时间复杂度\(O(nv)\),空间复杂度\(O(v)\)
\(F\)初始化:
- 明显的全部为\(0\)即可
代码实现(优化):
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define int long long
using namespace std;
typedef long long ll;
void read(int &sum)
{
sum=0;char last='w',ch=getchar();
while (ch<'0' || ch>'9') last=ch,ch=getchar();
while (ch>='0' && ch<='9') sum=sum*10+ch-'0',ch=getchar();
if (last=='-') sum=-sum;
}
int n,v;
struct node { int s,w; }a[10010];
int f[10000010];
signed main()
{
// freopen("M.in","r",stdin);
// freopen("M.out","w",stdout);
read(n),read(v);
for (int i=1;i<=n;i++) read(a[i].s),read(a[i].w);
for (int i=1;i<=n;i++)
{
for (int j=0;j<=v-a[i].s;j++)
f[j+a[i].s]=max(f[j+a[i].s],f[j]+a[i].w);
}
int mmax=0;
for (int i=0;i<=v;i++)
mmax=max(f[i],mmax);
printf("%lld",mmax);
// fclose(stdin);fclose(stdout);
return 0;
}
多重背包
题目描述:有\(n\)种物品和一个容量为\(v\)的背包,每种物品放一个进背包需要占用\(S_i\)的空间,可以得到\(W_i\)的价值,放入一些物品使得价值最大,每种物品有\(T_i\)个(空间大小要满足条件),求最大值
分析:
状态设计:
- 同完全背包,设\(F_{i,j}\)表示前\(i\)种物品恰好占用\(j\)空间物品的最大价值
转移方程:
- 这个问题可以看作,是一个完全背包,只不过把有每种\(v/S_i\)个,改成了\(T_i\),可以按完全背包朴素的方法做,\(F_{i,j}\)可能由\(F_{i-1,j-k*T_i}\),或者直接继承\(F_{i-1,j}\)
时空优化:
- 原时间复杂度为\(O(nv\sum_{i=1}^n(\frac{v}{S_i}))\),空间为\(O(nv)\)
- 考虑\(k\)的问题,其实最多就\(T_i\)个,考虑到用几个\(2^0\)~\(2^t\)可以表示出\(2^{t+1}-1\)里的所有数,所以将第\(i\)种物品变成\(\log_2T_i+2\)个物品,设\(\log_2T_i=g\),\(1\) ~ \(g-1\)是体积为\(2^t\)价值为\(W_i*2^t\)的物品,\(g\)个物品是体积为\(T_i-2^g+1\)价值为\(W_i*(T_i-2^g+1)\)的物品,于是这样就可以像\(01\)背包一样做了
- 滚掉\(i\)
- 时间复杂度\(O(nv\sum_{i=1}^n\log_2(\frac{v}{S_i}))\),空间复杂度\(O(v)\)
\(F\)初始化:
- 明显的全部为\(0\)即可
代码实现(优化):
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
void read(int &sum)
{
sum=0;char last='w',ch=getchar();
while (ch<'0' || ch>'9') last=ch,ch=getchar();
while (ch>='0' && ch<='9') sum=sum*10+ch-'0',ch=getchar();
if (last=='-') sum=-sum;
}
int f[101000];
struct node { int s,w; }a[1010*63];
int n,v;
int main()
{
// freopen("M.in","r",stdin);
// freopen("M.out","w",stdout);
int len;read(len),read(v);
for (int i=1;i<=len;i++)
{
int x,w,t;read(x),read(w),read(t);
int d=1;
while (d<=t)
{
n++;
a[n].s=d*x;
a[n].w=d*w;
d*=2;
}
a[n].s=x*(t-d/2+1);
a[n].w=w*(t-d/2+1);
}
for (int i=1;i<=n;i++)
{
for (int j=v;j>=a[i].s;j--)
{
f[j]=max(f[j],f[j-a[i].s]+a[i].w);
}
}
int ans=0;
for (int i=0;i<=v;i++) ans=max(ans,f[i]);
printf("%d",ans);
// fclose(stdin);fclose(stdout);
return 0;
}