多重背包的最原始的状态转移方程:
令 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;
}