一些想法:
现在是2024-3-17 09:27:36,好摸啊这两天。
解题报告:
背包问题模型是在有限的背包内,寻找一个贡献最大值的取物品的方案。可以利用二维状态 f[i][j] 来表示 前 i 个物品中,背包容量最大是 j 的,贡献最大的方案。背包方案的转移一般通过最后一个物品来分类。
最基础的背包问题。转移方程:
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++){
f[i][j]=f[i-1][j];
if(j>=v[i]) f[i][j]=max(f[i-1][j],f[i-1][j-v]+w);
}
滚动数组优化,观察转移方程,由于背包转移所需要的是在上一层,所以要反向遍历,防止使用已被更新的数据:
for(int i=1;i<=n;i++){
for(int j=m;j>=v;j--)
f[j]=max(f[j],f[j-v]+w);
}
完全背包问题,通过一层公式来进行转换
f[i][j]= max{f[i-1][j],f[i-1][j-1*v]+1*w, f[i-1][j-2*v]+2*w......f[i-1][j-k*v]+k*w};
f[i][j-v]= max{ f[i-1][j-1*v], f[i-1][j-2*v]+1*w......f[i-1][j-k*v]+(k-1)*w};
遂得转换方程如下
for(int i = 1; i<=n ;i++)
{
int v,w;cin>>v>>w;
for(int j = 0 ;j <= m; j++)
{
f[i][j]=f[i-1][j];
if(j>=v)f[i][j]=max(f[i][j-v]+w,f[i][j]);
}
}
滚动数组优化,观察原转移方程,由于方案转移所需要的数据在转移时是同一行,所以要从前往后遍历保证所需数据未被更新掉:
for(int i = 1; i<=n ;i++)
{
int v,w;cin>>v>>w;
for(int j = v ;j <= m; j++)
{
f[j]=max(f[j-v]+w,f[j]);
}
}
多重背包问题具有多种写法,首先最为朴素的三重循环,使用上一题完全背包中的朴素无公式转换的思路,直接循环拿取的物品数和应用限制物品数量的条件得到结果。
//s表示物品的数量,
for(int i = 1;i<=n ;i++)
{
cin>>v>>w>>s;
for(int j = 0;j<=m;j++)
for(int k = 0;k*v<=j&&k<=s;k++)
f[i][j]=max(f[i][j],f[i-1][j-k*v]+k*w);
}
优化:
把所有物品通过 s 来进行预处理,通过把 s 拆成二进制的每一位,遂可以直接用01背包完成题目,问题在于其空间需要扩大到 max{ V , log(S)*N}
int cnt = 0;
for(int i = 1;i <=n ;i++)
{
int a,b,s;cin>>a>>b>>s;
int k = 1;
for(int k = 1; k <= s; k<<=1)
cnt++,v[cnt]=k*a,w[cnt]=k*b,s-=k;
if(s>0)
cnt++,v[cnt]=s*a,w[cnt]=s*b;
}
单调队列优化(今天拉屎的时候终于悟了的解法)
当我们继续 尝试使用完全背包中的做法,我们会发现,他的最大值似乎不能通过一次减掉v来得到。而且其得到的最大值并不符合定义其最大值如下图
显然右图中的最大值并非定义最大值。
但是我们可以不断重复这个过程,直到所有矩形的全集被覆盖。那么在全集被渐渐覆盖的过程中,让这个体积只剩下余数,那么余数反过来慢慢往最开始的体积来加的话,在考虑的集合元素达到某一个数量 (题目中的物品数量s) 的时候,就可以开始得到其中的最大值,同时在不断更新集合中数据的过程中,把已经不符合条件的 (体积最小的元素) 退出,这就符合一个单调队列模型,就可以简单的对每一个数据是 m/v 的复杂度来处理 (v大小可省略为1),所以总的时间复杂度就是O(n*m) 的。
for(int i = 1; i<= n; i ++)
{
cin>>v>>w>>s;
for(int j = 0; j<v; j++)//遍历所有的余数,
{
int hh = 0 ,tt = -1;
for(int k = j; k <=m;k+=v )
{
if(hh<=tt && q[hh] < k-s*v) hh++;
//上次队尾的值没有补上现在的体积能放满的物品 所以补上后拿去和现在的值比较
while(hh<=tt && f[i-1][q[tt]]+(k-q[tt])/v*w <= f[i-1][k]) tt--;
q[++tt] = k;
f[i][k] = f[i - 1][q[hh]] + (k - q[hh]) / v * w;
}
}
}
同样的,分组背包只需要考虑不选和选哪个的问题,通过将01背包扩展,循环选哪个最优,三重循环结束战斗。
for(int i=0;i<n;i++)
for(int j=m;j>=0;j--)
for(int k=0;k<s[i];k++)
if(j>=v[i][k])
f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);
心得:
背包问题先写到这里,后面还有题目另开一篇博客。课好多感觉上不完,找不到时间来学算法竞赛了,烦的。