Matrix67要在下个月交给老师n篇论文,论文的内容可以从m个课题中选择。由于课题数有限,Matrix67不得不重复选择一些课题。完成不同课题的论文所花的时间不同。具体地说,对于某个课题i,若Matrix67计划一共写x篇论文,则完成该课题的论文总共需要花费Ai*x^Bi个单位时间(系数Ai和指数Bi均为正整数)。给定与每一个课题相对应的Ai和Bi的值,请帮助Matrix67计算出如何选择论文的课题使得他可以花费最少的时间完成这n篇论文。
输入格式:
第一行有两个用空格隔开的正整数n和m,分别代表需要完成的论文数和可供选择的课题数。
以下m行每行有两个用空格隔开的正整数。其中,第i行的两个数分别代表与第i个课题相对应的时间系数Ai和指数Bi。
输出格式:
输出完成n篇论文所需要耗费的最少时间。
输入样例#1:
10 3 2 1 1 2 2 1
输出样例#1:
19
【样例说明】
4篇论文选择课题一,5篇论文选择课题三,剩下一篇论文选择课题二,总耗时为2*4^1+1*1^2+2*5^1=8+1+10=19。可以证明,不存在更优的方案使耗时小于19。
【数据规模与约定】
对于30%的数据,n<=10,m<=5;
对于100%的数据,n<=200,m<=20,Ai<=100,Bi<=5。
这个题目容易想到是一个背包,每一篇论文都有在当前课题下写或不写两个选择,预处理数组f[i,j]表示第i个课题写第j个论文时的花费,ff[i,j]:=min(ff[i-1,j],f[i,j-1])即可。
后来研究了几组数据发现想的还是太少了,例如说对于样例数据的f[2,3]的值,结果不是6,9或许其他的什么,而是写两篇一课题,一篇二课题,而这样的话我的方程显然就不对了。
于是我又机智的写了一个在30%数据内的搜索完成这项功能,代码如下,也确实是30分。
var f:array[-1..200,-1..200] of int64; ff:array[-1..200,-1..3200] of int64; a:array[1..20] of int64; b:array[1..20] of int64; n,m,i,j,k:longint; minn:int64=100000000; function min(x,y:int64):int64; begin if x>y then exit(y) else exit(x); end; function mi(a,b:int64):int64; var t,y:int64; begin t:=1; y:=a; while b<>0 do begin if (b and 1)=1 then t:=t*y; y:=y*y; b:=b shr 1; end; exit(t); end; procedure five; var i,j,k,l,o:longint; begin for i:=0 to n do for j:=0 to n do for k:=0 to n do for l:=0 to n do for o:=0 to n do if (i+j+k+l+o=n) then minn:=min(minn,f[1,i]+f[2,j]+f[3,k]+f[4,l]+f[5,o]); end; procedure four; var i,j,k,l,o:longint; begin for i:=0 to n do for j:=0 to n do for k:=0 to n do for l:=0 to n do if (i+j+k+l=n) then minn:=min(minn,f[1,i]+f[2,j]+f[3,k]+f[4,l]); end; procedure three; var i,j,k,l,o:longint; begin for i:=0 to n do for j:=0 to n do for k:=0 to n do if (i+j+k=n) then minn:=min(minn,f[1,i]+f[2,j]+f[3,k]); end; procedure two; var i,j,k,l,o:longint; begin for i:=0 to n do for j:=0 to n do if (i+j=n) then minn:=min(minn,f[1,i]+f[2,j]); end; procedure one; var i:longint; begin minn:=f[1,n]; end; begin readln(n,m); fillchar(f,sizeof(f),0); fillchar(ff,sizeof(ff),0); for i:=1 to m do readln(a[i],b[i]); for i:=1 to m do for j:=0 to n do f[i,j]:=a[i]*mi(j,b[i]); if m=1 then one; if m=2 then two; if m=3 then three; if m=4 then four; if m=5 then five; if minn=100000000 then begin for i:=1 to m do for j:=0 to n do ff[i,j]:=min(f[i,j-1],f[i-1,j]); minn:=f[m,n] end; writeln(minn); end.
其实实际上也就是一个背包,再多考虑一种循环,表示当现在一共写了j篇论文时,用k篇是在当前课题下写的,这样就可以考虑到所有的情况了,代码如下:
{AC} var f:array[-1..200,-1..200] of int64; //前i个课题写了j个论文的最小花费 a:array[1..20] of int64; b:array[1..20] of int64; n,m,i,j,k:longint; function min(x,y:int64):int64; begin if x>y then exit(y) else exit(x); end; function mi(a,b:int64):int64; var t,y:int64; begin t:=1; y:=a; while b<>0 do begin if (b and 1)=1 then t:=t*y; y:=y*y; b:=b shr 1; end; exit(t); end; //快速幂,其实对于这道题的数据加不加用处不大 begin readln(n,m); fillchar(f,sizeof(f),0); for i:=1 to m do readln(a[i],b[i]); for i:=1 to n do f[i,0]:=0; for i:=1 to n do f[1,i]:=a[1]*mi(i,b[1]); //预处理最底层的元素 for i:=2 to m do //一共选了多少课题 for j:=1 to n do //一共写了多少论文 begin f[i,j]:=f[i-1,j]; //一定要先赋值,否则可能得出不正确答案 for k:=1 to j do //在当前课题下写了多少论文 f[i,j]:=min(f[i,j],f[i-1,j-k]+a[i]*mi(k,b[i])); end; writeln(f[m,n]); end.
这次的惨痛教训表明我的DP思想还不完善,练习还太少,还需要更多的练习和总结。
加油,争取拿出联赛一等!
原文:http://www.cnblogs.com/yangqingli/p/4803029.html