【集训题 糖果分发、集装箱】一类特殊动态规划的优化方法

先拿一道题(糖果分发)做引子(by Ly)

大意:给你n个物品,每个物品有个重量,你可以取m次,每次取出的物品重量之和不超过一个给定的数k,而且每次你只能在一段区间内选择,每次选的区间不能相交,求最多能取到的物品数。需要注意的是,题目中要求你在“一段区间内”取,但并不要求你取走这个区间内的所有物品

数据范围:1<=n<=2000,1<=m<=50,1<=k<=10000

分析:

   算法显然是dp,首先容易得到一个O(n*m*k)的方程,但显然是过不了的

   上面那个方程其实很废,记录了大量的冗余状态(尽管背包类题目大都有状态分布稀疏的特点)

   当然,我们可以dfs预处理出有用的状态再做,这样的确能快不少,但不是本文重点

   为什么硬要将当前背包剩余重量和取的次数加到状态中?仔细考虑这个问题,发现背包当前容量和已经取了几次其实并不会造成多大的后效性,问题要求的“第一关键字”是我们用掉的背包的个数(就是取了几次啦)

算法:

   记f[i,j]表示前i个物品中,选了j个物品,最少的取的次数,g[i,j]表示在f[i,j]最少的条件下,最后一次取了多重

   考虑第i个物品时,用f[ii,j]+取第i个物品来转移,更新f[ii+1,j+1],如果g[ii,j]+w[i]没超过k,则给g[ii,j]加上w[i],否则给f[ii,j]+1

   最后ans=j|max{f[n,j]<=m}

   时间复杂度就降到了O(n^2),完美解决了这道题

小结:

   这个解法实际上是把原方程要记的值表示到了状态中,把原状态表示到了要记的值里去,由于题目中要求的数的特殊性,这样做是正确的

 

这里用到了一个方法,将原dp方程稀疏的状态表示到新方程记的值里去,而把原方程记的值表示到新方程的状态中来

 

再看另外一道题:

(集装箱)

大意:给你n个物品,要求你按顺序将这n个物品放入集装箱中,你只能在两个打开着的集装箱中做选择,放入任意一个中,集装箱的容积相同,满了你就可以把这个封装,同时开一个新的,要求使用尽量少的集装箱

数据范围:n<=2000,集装箱容积L<=2000

分析:

   首先容易得到一个O(n*L^2)的dp方程,f[i,j,k]表示前i个物品,第一个箱子装了j,第二个箱子装了k时最少所用集装箱数

   同样是稀疏的状态分布,那我们就可以考虑上题类似的优化方法了,将一维状态表示到值中

算法:

   记f[i,j]表示前i个物品,第一个箱子装了j所用的最少集装箱数,g[i,j]表示在f[i,j]的情形下,第二个集装箱已经用了的最小容积

   转移跟上一题类似,也是考虑每个物品装在哪个箱子里面,超过容量限制就给f[i,j]+1

 

总结:

   我们可以从这两道题中看出,在有效状态分布非常稀疏的dp方程中,有时候是可以将缩掉一维状态,或是将结果表示到状态中,状态记到结果里去,从而达到优化方程的目的。

   值得一提的是,可以这样优化的方程通常都是要求类似“使用尽量少的背包装下一些物品”之类的东西,结果是连续的而且一般很小,状态则是零散的,而且每次转移答案最多+1

   优化算法,要从问题的本质特点出发,充分挖掘、利用性质,才能达到一个好的效果

 

附关键代码

第一题:

第二题:

   第二题std用的是dfs预处理状态,我yy出了这个怪怪的算法一不小心虐了std,爽

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值