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