背包学得太差了要好好复习总结一下 : )
## $01$/完全 背包
背包问题:有一些物品,每个物品有花费和价值,一般来说求的是在花费不超过给定数的前提下求最大的价值.
一般来说会省略第一维,但是在一些问题转化成的背包问题中不要忘了这一维可能又会被利用起来.
//01背包 v表示花费 w表示价值
go(i,1,n)
yes(j,t,a[i].v)
f[j]=max(f[j],f[j-a[i].v]+a[i].w);
//完全背包
go(i,1,n)
go(j,a[i].v,t)
f[j]=max(f[j],f[j-a[i].v]+a[i].w);
## 多重背包
每种物品有$d_i$件.
1.暴力拆分.
将物品拆成(sum d_i)件,然后跑(01)背包.
2.二进制拆分.
将物品拆成(sum log d_i)件,然后跑(01)背包.
3.单调队列优化.
我又忘了(.jpg),而且发现没有好好讲过单调队列优化多重背包,所以这里详细讲下叭.
发现(f_{i,j})一定是从(f_{i,j-kcdot v_i})转移而来,(f_{i,j+1})一定从(f_{i,j+1-kcdot v_i})转移而来,这两者的候选集合并不重合,也就是说当(i)确定时,它们两个是不互相影响的.
考虑将(j)按照除以(v_i)的余数分个类,一类里的某个状态(j)的候选答案集合一定也是属于该状态,所以我们依次在每一类里从小到大地求(f_j).
具体来说,我们把枚举(j)的一层变成先枚举余数(u),再从小枚举(j\%v_i=u)的(j),其实这里只要枚举一个(pin[1,(m-u)/v_i]),这样枚举得到的状态(j)就是(pcdot v_i+u).
转移:(f_{pcdot v_i+u}=max_{ p-d_ileq kleq p-1}{f_{kcdot v_i+u}+(p-k)cdot w_i})
将外层循环(i,u)看成定值,只要维护一个随着(k)的减小,(f_{kcdot v_i+u}-kcdot w_i)递减的单调队列就可以了.
il int calc(Ri k,Ri i,Ri u){return f[k*a[i].v+u]-k*a[i].w;}
go(i,1,n)
{
Ri v=a[i].v,w=a[i].w,d=a[i].d;
go(u,0,v-1)
{
Ri l=1,r=0,mx=(m-u)/v;
yes(p,mx-1,max(mx-d+1,0))
{
while(l<=r && calc(q[r],i,u)<=calc(p,i,u))--r;
q[++r]=p;
}
yes(p,mx,1)
{
if(p-d>=0)
{
while(l<=r && calc(q[r],i,u)<=calc(p-d,i,u))--r;
q[++r]=p-d;
}
while(l<=r && q[l]>p-1)++l;
if(l<=r)f[p*v+u]=max(f[p*v+u],calc(q[l],i,u)+p*w);
}
}
}
## 树形背包/有依赖的背包问题
例题:洛谷$1024$ 选课.
大致思路就是,对每个子树都(dp)一遍,将答案集中要子树的根结点,层层解决.具体来说,设(f_{i,j})表示在以(i)为根的子树内选择了(j)个物品的最大价值(这里规定物品的体积都为(1)),转移的话就直接枚举子结点(k)用(f_{k,j'})转移即可,注意枚举顺序是(01)背包的枚举顺序.
## 背包方案计数
$01$背包计数,例题:洛谷$1164$ 小$A$点菜.
设(f_{i,j})为前(i)个物品,选了的物品的体积总和为(j)的方案数.转移考虑能不能和选不选当前的物品就行.显然是可以省略掉第一维的,并且省略之后转移更简单一些.
完全背包改下枚举顺序就可以了叭.
## 习题
**希望有神仙可以推荐背包题!!就稍微有点绕的一看看不出是背包的那种就行!!**
(NOIp2018) 货币系统
考虑删掉一种货币的条件是它能被其他货币表示出来,设(f_i)表示数(i)能否被表示出来,完全背包一下,(over).
(NOIp2014) 飞扬的小鸟
一道完全背包和(01)背包的混合题(QwQ).设(f_{i,j})表示到达位置(i),高度为(j)所需的最小点击数目.似乎细节很多,虽然去年写过不过还是打算再写一遍(QwQ).但我现在暂时不想写了(QwQ).