只需要对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.