POJ 2063 Investment 完全背包
题目链接:http://poj.org/problem?id=2063
今天果然是卡题的一天。白天被hdu那道01背包的变形卡到现在还没想通就不说了,然后晚上又被这道有个不大也不小的坑的完全背包卡了好久。这题主要是说让你选择不同的债券(每种债券的费用和年利率的属性),然后n年后使得本金最大,如果仅仅是问一年的话就是个裸完全背包的题了,不过它是n年,每年得到的总利息都会加入到本金中变为下一年的本金,知道了这个后就很好处理了,在这道题里每年变化的本金就是背包容量,然后债券那两个属性就是物品的费用(or体积)和价值(or重量)。
一开始我快速码好后提交结果各种MLE,TLE,当注意到每种债券的费用都是1000的倍数时我改了下,大概主函数如下:
1 while(y--){ 2 memset(dp,0,sizeof(dp)); 3 for(i=1; i<=d; ++i) 4 for(j=cost[i]; j<=capital; j+=1000) 5 dp[j]= max(dp[j],dp[j-cost[i]]+value[i]); 6 capital+= dp[capital/1000*1000]; 7 }
提交后还是TLE,我就奇怪了,虽说和标程的常规方法不同,但初步分析下复杂度应该是一样的啊,j 每次都是1000地递增的,后来经过用标程提交几番痛苦的摸索后终于知道TLE的根本原因是memset函数!因为我的dp数组开到了 int dp[7000006] 这么大,而y的上限是40,相乘后就是280000000,1s根本没法承受!本作为辅助的步奏却大大超过了主算法的时间,所以我干脆用for手动清零算了,改成这样:
1 // memset(dp,0,sizeof(dp)); 2 for(j=0; j<=capital; j+=1000) 3 dp[j]= 0;
然后华丽地RE了,看来数组dp[7000006]竟然还不够大,然后我稍微开大了一点点,就MLE了,看来这个是30000KB的临界值了(后来我才发现这题的本金即背包容量会达到 5kw!即使数组勉勉强强开得下,也绝对远远超过了大多数题目的内存限制),没办法了,即使我这种方法的技巧性再强,还是会被各种TLE,MLE,RE卡死,看来真的需要常常规规的去做了。需要把债券的费用和本金缩小1000倍,这样子背包容量的 j 循环就可以连续的了,数组的空利用率也大大提交(T.T 和我上面的做法相比就可以看出来)。这样做的话maxn只需要开到50000即可,实际放大后就是5kw,如果硬要把maxn开到百万级别以上的话,memset就不能用,必须手动清零,否则时间就会浪费在为数组那些实际上不会用上的空间进行清零,即使memset按字节来处理也忍受不了千万级别的1s时限,基于各种修改过后的代码如下:
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<ctime> 5 using namespace std; 6 const int maxn= 5000000; 7 8 int dp[maxn+3], cost[12],value[12]; 9 10 int main(){ 11 int t,capital,y,d,i,j,tmp; 12 scanf("%d",&t); 13 while(t--){ 14 scanf("%d%d%d",&capital,&y,&d); 15 for(i=1; i<=d; cost[i]/= 1000, ++i) 16 scanf("%d%d",cost+i,value+i); 17 double b = clock(); 18 while(y--){ 19 // memset(dp,0,sizeof(dp)); 这个没有错,但上面的 maxn一定不能太大! 20 tmp= capital/1000; 21 for(j=0; j<=tmp; ++j) 22 dp[j]= 0; 23 for(i=1; i<=d; ++i) 24 for(j=cost[i]; j<=tmp; ++j) 25 dp[j]= max(dp[j],dp[j-cost[i]]+value[i]); 26 capital+= dp[tmp]; 27 } 28 double c = clock(); 29 // printf("%.f\n",c-b); 30 printf("%d\n",capital); 31 } 32 return 0; 33 }
交之,63ms Accepted。
从这道题中我得到的教训就是为了不用推倒重来而使用上那一点点小技巧(即j+=1000),却使数组空间大大浪费掉(不是么,1/1000的利用率 T.T),然后还带来了MLE or RE的不可避免的风险;第二个学到的就是,memset函数也有可能是TLE的根源,要看清数组会有多大,所以以后当TLE时检查了scanf里有没漏 & 取址符或long long的读入有误,或者少了个EOF,或者其他等等等等非算法问题造成的超时后,不妨看看是否有可能数组太大,memset会不会做了多余的工作(为数组实际用不上的空间进行操作)等等。