单调队列优化多重背包

2 篇文章 0 订阅

多重背包的最原始的状态转移方程:

令 c[i] = min(num[i], j / v[i])

f[i][j] = max(f[i-1][j-k*v[i]] + k*w[i]) (1 <= k <= c[i]) 这里的 k 是指取第 i 种物品 k 件。

如果令 a = j / v[i] , b = j % v[i] 那么 j = a * v[i] + b.

这里用 k 表示的意义改变, k 表示取第 i 种物品的件数比 a 少几件。

那么 f[i][j] = max(f[i-1][b+k*v[i]] - k*w[i]) + a*w[i] (a-c[i] <= k <= a)

可以发现,f[i-1][b+k*v[i]] - k*w[i] 只与 k 有关,而这个 k 是一段连续的。我们要做的就是求出 f[i-1][b+k*v[i]] - k*w[i] 在 k 取可行区间内时的最大值。

k 取可行区间内时的最大值。k 取可行区间内时的最大值。k 取可行区间内时的最大值。
这句话特别重要,两个晚上理解了这句话。
m[i] = min(n[i], j / v[i])。
F[i][j]就是求这个队列最大长度为m[i] + 1时,队列中元素的最大值,加上a * w[i]。

这就可以使用单调队列优化。

for (int i = 1; i <= n; ++i) {
    Ni = Num[i]; Vi = V[i]; Wi = W[i];
    for (int j = 0; j < Vi; ++j) {
        Head1 = Tail1 = 0;
        Head2 = Tail2 = 0;
        Cnt = 0;
        for (int k = j; k <= m; k += Vi) {
            if (Tail1 - Head1 == Ni + 1) {
                if (Q2[Head2 + 1] == Q1[Head1 + 1]) ++Head2;
                ++Head1;
            }
            t = f[k] - Cnt * Wi;
            Q1[++Tail1] = t;
            while (Head2 < Tail2 && Q2[Tail2] < t) 
               --Tail2;

            Q2[++Tail2] = t;

            f[k] = Q2[Head2 + 1] + Cnt * Wi;//可行性区间为cnt 
            ++Cnt;
        }
    }
}

f[i][j] = max(f[i-1][b+k*v[i]] - k*w[i]) + a*w[i] (a-c[i] <= k <= a)

例题
http://www.cnblogs.com/JoeFan/p/4168024.html

codevs
5429

#include<iostream>
using namespace std;
int f[10000],num[10000],v[10000],w[10000];int ps,qs,pe,qe;
int q1[10000],q2[10000];
int main()
{  int n,m;cin>>n>>m;
   for (int i=1;i<=n;++i)
   {  cin>>w[i]>>v[i]>>num[i];
   }
   int ans=0;
   for (int i=1;i<=n;++i)
   {  for (int j=0;j<w[i];++j)
      {  ps=qs=1;//q为单调,q2单调 
         qe=pe=0;
         for (int k=j,cnt=0;k<=m;++cnt,k+=w[i])
         {  if (pe-ps==num[i])
            {  if (q1[ps]==q2[qs])
                 ++qs;
               ++ps;
            }
            int t=f[k]-cnt*v[i];
            q1[++pe]=t;
            while (qs<=qe&&t>q2[qs])
              --qe;
            q2[++qe]=t;
            f[k]=q2[qs]+cnt*v[i];
         }
      }
      ans=max(ans,f[m]);
   }
   cout<<ans;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值