背包
0/1背包

$F[i,j]=max \begin{cases} F[i-1,j]\\ F[i-1,j-V_i]+W_i &\text{if } j \ge V_i \end{cases}$
memset(f,0xcf,sizeof(f));
f[0][0]=0;
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++)
f[i][j]=f[i-1][j];
for(int j=v[i];j<=m;j++)
f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
}
我们说这个这个,你第一次就学不会的东西,后面往往学N次也学不会。是真的吗?
当然是真的。
因为他跟你八字不合啊。
所以同学们注意了以后打死学不会的东西,就别学了。
背包虽然和我八字不合,但还是说一下吧。方程很简单不说了。然后实现的话,你去先遍历i,也就是多少个物品了对不对嗯?然后你还要j,$F[i,j]$表示在前i个物品中总体积为j的物品价值最大者。你遍历到i了对嘛,怎么搞j呢?
我们发现,如果不选该物品,那就跟i-1的j体积一样;要选就要从$ F[i-1,j-V_i]$转移过来。可不可以直接一个j循环从0到m搞完呢?那样你就会成为RE姐
所以我们就来这么搞,先给全部初始化为$F[i-1,j]$,然后具备资质的我们再去看$ F[i-1,j-V_i]$会不会更大嘛。
请记住这才是最根本的背包dp。至此,状态明确,思路明晰。
接下来,我们要开始让状态不那么明确了。
为了节省可怜的空间,我们发现每一阶段的i只与上一阶段的i-1有关。
啊蛤蛤!那前面那么多空间岂不是浪费了!
拿两个数组在那里交替着滚就可以了。
int f[2][MAX_M+1];
memset(f,0xcf,sizeof(f));
f[0][0]=0;
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++)
f[i&1][j]=f[(i-1)&1][j];
for(int j=v[i];j<=m;j++)
f[i&1][j]=max(f[i&1][j],f[(i-1)&1][j-v[i]]+w[i]);
}
int ans=0;
for(int j=0;j<=m;j++)
ans=max(ans,f[n&1][j]);
滚动数组。
啊那有的人又要说了搞这么麻烦干什么干脆第一维也扯来甩了嘛
(实际上扯了第一维更麻烦呢)
你就是想着一个数组来自己更新自己嘛。好啊。
但是这种一般会有大病哦!
我们先看看正解
int f[MAX_M+1];
memset(f,0xcf,sizeof(f));
f[0]=0;
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]);
int ans=0;
for(int j=0;j<=m;j++)
ans=max(ans,f[j]);
嘿我们注意到他的j是倒叙循环的!为什么呢?

那么,很显然他就会重复更新嘛。但是你每个东西只能用一次哦,不可以装很多次!
因此倒序是正确的
完全背包

说啥啊,这个
我们小学的时候有一篇课文,叫《玩出了名堂》
是的,每一个伟大的发现,都来源于无数的失败,而有些正是出于意外
所以,小朋友们要保持热爱尝试的精神哦。
int f[MAX_M+1];
memset(f,0xcf,sizeof(f));
f[0]=0;
for(int i=1;i<=n;i++)
for(int j=v[i];j<=m;j++)
f[j]=max(f[j],f[j-v[i]]+w[i]);
int ans=0;
for(int j=0;j<=m;j++)
ans=max(ans,f[j]);
上面的代码改为正序即可。相当于自动去寻找在可以无限更新一个物品的情况下的正解。
欧耶
多重背包

我们可以把多个同一种物品看成多种物品来01背包。暴力哪里好啊哪里好?暴力就是好啊就是好!
同学们可能发现蓝书上提到了额二进制拆分法,是怎么回事呢?
就是把第i个物品拆成$log C_i$个,他们是$2^0*V_i,2^1*V_i,....,2^{log C_i}*V_i,(C_i-logC_i)*V_i$
每个数拆完之后放那儿一起暴力01背包
蓝书上那坨就是这样做和全部拆完结果一样的证明
是的,dp的本质没有改变
还有个凑钱题,大概意思就是
开used数组记录在当前阶段下$F[j]$为true至少需要多少第i币
与之前的一维01背包一样,这个数组也是空间节省的结果,会随着阶段改变
考虑贪心。一个数额可能是多种方式凑出的,我们找到用当前币最少的方案,其他的扔了。
所以只在特定条件下更新状态。详见蓝书或代码。
int used[100010];
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++)used[j]=0;
for(int j=a[i];j<=m;j++)
if(!f[j]&&f[j-a[i]]&&used[j-a[i]]<c[i])
f[j]=true;
used[j]=used[j-a[i]]+1;
}
分组背包

有完没完了啊我草?
下次再也不给你们写题解了又浪费我一天时间
矮其实这个不难吧和01差不多
$F[i,j]=max \begin{cases} F[i-1,j]\\ \text{max}_{1 \le k \le C_i} \space F[i-1,j-V_{ik}]+W_{ik}\end{cases}$
然后就是三层循环的事情了。没啥说的了。
memset(f,0xcf,sizeof(f));
f[0]=0;
for(int i=1;i<=n;i++)
for(int j=m;j>=0;j--)
for(int k=1;k<=c[i];k++)
if(j>=v[i][k])
f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);

浙公网安备 33010602011771号