只需要对John的付款数做一次多重背包,对shopkeeper的找零钱数做一次完全背包即可。
最重要的是上界的处理。可以注意到,John的付款数最多为maxv*maxv+m,也就是24400元。同理,shopkeeper找钱最多的数目为maxv*maxv.
证明如下:
如果John的付款数大于了maxv*maxv+m,即付硬币的数目大于了maxv,根据鸽笼原理,至少有两个的和对maxv取模的值相等,也就是说,这部分硬币能够用更少的maxv来代替。证毕。
代码:
Program Fewcoins;//By_Thispoet
Const
maxn=120;
maxm=200000;
Var
i,j,k,m,n,maxi,gone,ans :Longint;
f,g :Array[0..maxm]of Longint;
c,v :Array[0..maxn]of Longint;
Function Min(i,j:Longint):Longint;
begin
if i<j then exit(i);exit(j);
end;
BEGIN
readln(n,m);
for i:=1 to n do
begin
read(v[i]);
if v[i]>maxi then maxi:=v[i];
end;
fillchar(f,sizeof(f),1);
f[0]:=0;gone:=0;
for i:=1 to n do read(c[i]);
maxi:=m+maxi*maxi;
for i:=1 to n do
begin
k:=1;
while k<=(c[i]>>1) do
begin
for j:=gone downto 0 do
if (j+v[i]*k<=maxi)and(f[j]+k<f[j+v[i]*k]) then
begin
f[j+v[i]*k]:=f[j]+k;
if (j+v[i]*k>gone) then gone:=Min(j+v[i]*k,maxi);
end;
k:=k<<1;
end;
k:=c[i]-(k>>1);
for j:=gone downto 0 do
if (j+v[i]*k<=maxi)and(f[j]+k<f[j+v[i]*k]) then
begin
f[j+v[i]*k]:=f[j]+k;
if (j+v[i]*k>gone) then gone:=Min(j+v[i]*k,maxi);
end;
end;
dec(maxi,m);
fillchar(g,sizeof(g),1);
g[0]:=0;
for i:=1 to n do
begin
k:=1;
while k<=(maxi div v[i])>>1 do
begin
for j:=maxi downto 0 do
if (j+v[i]*k<=maxi)and(g[j]+k<g[j+v[i]*k]) then g[j+v[i]*k]:=g[j]+k;
k:=k<<1;
end;
k:=(maxi div v[i])-(k>>1);
for j:=maxi downto 0 do
if (j+v[i]*k<=maxi)and(g[j]+k<g[j+v[i]*k]) then g[j+v[i]*k]:=g[j]+k;
end;
ans:=maxlongint;
for i:=maxi+m downto m do
ans:=Min(f[i]+g[i-m],ans);
if ans<100000 then writeln(ans) else writeln(-1);
END.