解“POJ 1190 生日蛋糕”有感

先简单说一下题意吧,题目其实可以抽象为一个数学问题(当然编程解决的话可能不需要用这样的思路):

                                   min   R12+2R1h1+2R2h2+…+2Rmhm   s.t.   R12h1+R22h2+…+Rm2hm=n

如果将R1到Rm看做从下到上的m层蛋糕的半径,h1到hm 看做相应的高度,满足Ri>Ri+1,hi>hi+1便可知题目就是要求解出在限制整个蛋糕体积为n的条件下,整个蛋糕所能达到的最大表面积(不包括最下面一层的底面,另外不考虑常量/pi的因素)。

题目中说明了R、h、m、n均为整数,于是便想到使用搜索的方法来进行求解:枚举所有m层可能的R和h,满足限制条件的话就求解出相应的表面积,进而得到表面积的最小值。

于是我开始编程的思路就是从最上面一层(半径和高度最小的m层)开始,用(1,1)来初始化它的半径和高度,在递归方面用一个二层循环分别递增当前层的半径和高度,下面每一层的初始值都是上面一层半径和高度加1。在终止条件方面,一开始是判断从上往下的第1层到第i层的体积之和是否有超过i*n/m,后来觉得这个界太松,又改为令从上到下的i+1层到最底下一层的半径和高度依次为上一层对应数值加1,计算整个蛋糕的体积是否大于n。现在看来这个思路本身就比较混乱,结果也是如此,调了很多小错误之后,还是只能解决一下小规模的问题,本题Discuss中有人提到的n=7523,m=5的情况等了一分多种都没出结果。

于是只能想Discuss中贴代码的牛人求助,最终参考了http://acm.pku.edu.cn/JudgeOnline/showmessage?message_id=144966中的代码,理解了他的思路之后自己又重新写了一遍,调了一些错误之后终于混了过去,这里对于一开始的失误和人家程序中值得学习的地方进行一下总结:

1、从实际的编程效果来看,从底层到上层进行递归貌似思路更通顺一些,程序也更加简洁,当然我在AC之后也尝试了从上到下的递归顺序,效率好像差一些(应该是我的程序问题),这一点是我觉得就是说在编程解题这方面不要局限于某个一开始想到的思路,如果那个思路越想越混乱的话还是要看看有没有其它更加简洁的方向;(底下的叙述都是按照从底层向上层的递归方向)

2、在进入递归程序之前可以先通过init()进行一定的预处理来记录必要的信息,减少后面的重复计算。像是本题中,就可以求出从上到下第1层到第i层所需要的最小体积minv[1-20],和所需要的最小表面积mins[1-20],(通过R,h>=层数的限制进行计算),这些信息可以在后面的剪枝中得到使用;

3、本题最重要的部分应该就是剪枝了,说实话由于以前编程经验太少,几乎没有正儿八经编写过剪枝的程序(印象中只有/alpha-/beta剪枝,现在估计也写不出来了,sigh……),一开始的剪枝思路,除了上面提到的“终止条件”之外,另一个想到的就是在最后一层中对奇偶性进行分类讨论(写完后发现对于程序加速几乎没有什么帮助),回想一下发现还是自己对剪枝的理解不够准确,从上面Discuss所附源代码中思路以及/alpha-/beta剪枝来看,剪枝一般来说不会局限于某一个小分支的不同情况(像是前面奇偶性的思路就只能针对最后一个节点),而是倾向于“高瞻远瞩”的方式;比如说到了某个节点就可以判断出这条分支后的所有路线得到的结果都不符合一直条件或者不如当前最优解之类的,Discuss版的那个程序中leftv < minv[level]就属于前者,而s + mins[level] >= ans || 2*leftv/lastR + s >= ans就属于后者,这里也看到了minv和mins的用处;

4、这道题的求解中涉及了少量的数学推导,但都不是太复杂,以后也要做好这方面的准备;另外在程序风格上面,由于递归函数是栈式调用,因此可以通过传参的设置来减少函数内部的计算量(一般不会只传递一个参数);

总的来说这道题还是让我对剪枝有了一定的理解,当然以后还是要多多练习……

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值