这题如果不需要输出方案会简单一些,虽然我在洛谷上AC了但是我的输出方案并不一定正确,纯粹是数据的问题,希望AC的巨佬能贴出来让大家学习一下(网上实在没有洛谷这个版本的题解)
而且这题的重点应该是多重背包的优化。
一般的多重背包是这样的:
void solve()
{
memset(f,0x3f,sizeof(f));
f[0]=0;
for(int i=1;i<=N;i++)
for(int j=M;j>=b[i];j--)
for(int k=1;k<=min(c[i],j/b[i]);k++)
f[j]=min(f[j],f[j-b[i]*k]+k);
cout<<f[M];
}
可以看出转移方程为:f[x]=min{ f[x-bi*j]+j } ( 1<=j<=min{ci,x/bi} ),这题数据肯定超时。
网上看了很久,大概觉得这题单调队列优化是这样的:
现在枚举到第i种硬币,面值bi,个数ci。回顾上面的状态转移方程,发现f[j]和f[j-bi*k]的关联不大,这里就把所有要凑的面值x按照对bi取模的结果分租。
假设取模余d。那么bi*0+d,bi*1+d,bi*2+d....bi*j+d,都是一组的。假设x=bi*j+d,那么满足下面的关系:
f[x] = f[bi*j+d] = min{ f[bi*k+d] + (j-k) }。 其中,j-k<=ci。
也许细心的大佬会觉得应该是 j<=ci,否则万一k和j都取到了,可能会大于c[i]。确实,在上面的代码里,j倒着枚举就是考虑到了这种情况(正着枚举是数量无限的情况)。不过下面在进队时的细节可以避免它。
转化一下: f[bi*j+d] = min{ f[bi*k+d] - k } + j (j-k<=ci) 。
如果把 bi*0+d~bi*k+d 都入队,那么就转变成了在一个队列里求最小值,可以用单调队列解决。
入队的细节:上面的代码倒着枚举是因为要用到未更新时的数据。类似地,我们也在更新前把f[bi*j+d]入队,再对它更新。
至于记录完全是玄学了,确实不会。不介意可以拿这个混过去。
#include<bits/stdc++.h>
using namespace std;
const int MAXN=205;
const int MAXM=20005;
int N,M,b[MAXN],c[MAXN],f[MAXM],ans[MAXN];
struct data{int k,num;};
data q[MAXM],record[MAXM][2];
char C;
void scan(int &x)
{
for(C=getchar();C<'0'||C>'9';C=getchar());
for(x=0;C>='0'&&C<='9';C=getchar()) x=x*10+C-'0';
}
char Num[10]; int cnt;
void print(int x)
{
cnt=0;
if(!x) Num[cnt++]='0';
while(x) Num[cnt++]=x%10+'0',x/=10;
while(cnt--) putchar(Num[cnt]);
putchar(' ');
}
void out(int x)
{
if(!x) return;
data t0=record[x][0],t1=record[x][1];
if(!t1.num)
{
ans[t0.num]+=t0.k;
out(x-b[t0.num]*t0.k);
}
else
{
if(ans[t1.num]+t1.k>c[t1.num])
{
ans[t0.num]+=t0.k;
out(x-b[t0.num]*t0.k);
}
else
{
ans[t1.num]+=t1.k;
out(x-b[t1.num]*t1.k);
}
}
}
int main()
{
int i,j,d,x,L,R;
scan(N);
for(i=1;i<=N;i++) scan(b[i]);
for(i=1;i<=N;i++) scan(c[i]);
scan(M);
memset(f,0x3f,sizeof(f));
f[0]=0;
for(i=1; i<=N; i++) //枚举第i种硬币
for(d=0; d<b[i]; d++) //枚举除以该硬币面额的余数
{
L=1; R=0; //维护一个队首元素(num) 最小的队列
for(j=0;;j++) // x=b[i]*j + d ,枚举j
{
x= j*b[i]+d; //此时的总金额
if(x>M) break;
while( L<=R && j-q[L].k>c[i] ) L++; //判断是否超过c[i]
while( L<=R && f[x]-j<=q[R].num ) R--; //判断是否满足单调
q[++R]=( (data) {j,f[x]-j} ); //更新前入队
if(q[L].num+j<f[x])
{
f[x]=q[L].num+j;
if(!record[x][0].num) record[x][0]=((data){j-q[L].k,i});
else record[x][1]=((data){j-q[L].k,i});
}
}
}
print(f[M]); putchar('\n');
out(M);
for(i=1;i<=N;i++) print(ans[i]);
return 0;
}