深度优先搜索-生日蛋糕

生日蛋糕 (百练1190)
要制作一个体积为Nπ的M层生日蛋糕,每层都是一个圆柱体。
设从下往上数第i(1 <= i <= M)层蛋糕是半径为Ri, 高度为Hi的圆柱。当i < M时
,要求Ri > Ri+1且Hi > Hi+1。
由于要在蛋糕上抹奶油,为尽可能节约经费,我们希望蛋糕外表面(最下一层的下底面除外)
的面积Q最小。令Q = Sπ
请编程对给出的N和M,找出蛋糕的制作方案(适当的Ri和Hi的值),使S最小。
(除Q外,以上所有数据皆为正整数)

输入
有两行,第一行为N(N <= 10000),表示待制作的蛋糕的体积为Nπ;第二行为M(M <= 20),
表示蛋糕的层数为M。
输出:仅一行,是一个正整数S(若无解则S = 0)。

输入:
100
2
输出:
68

思路:
蛋糕的体积:V=Nπ=πR21H1+πR22H2+...+πR2nHn
则:N=R21H1+R22H2+...+R2nHn
其中(R1>R2>R3>...>Rn

蛋糕的表面积:Q=Sπ=πR21+2πR1H1+2πR2H2+...+2πRnHn
则:S=R21+2R1H1+2R2H2+...+2RnHn
其中(R1>R2>R3>...>Rn且H1>H2>H3>...>Hn)

当N取最大值10000时,最大半径是当h=1时,即r=100。同理,最大的h=10000

深度优先搜索,枚举什么?
枚举每一层可能的高度和半径。
如何确定搜索范围?
底层蛋糕的最大可能半径和最大可能高度
搜索顺序,哪些地方体现搜索顺序?
从底层往上搭蛋糕,而不是从顶层往下搭

剪枝1:搭建过程中发现已建好的面积已经超过目前求得的最优表面
积,或者预见到搭完后面积一定会超过目前最优表面积,则停止搭建(最优性剪枝)
剪枝2:搭建过程中预见到再往上搭,高度已经无法安排,或者半径
已经无法安排,则停止搭建(可行性剪枝)
剪枝3:搭建过程中发现还没搭的那些层的体积,一定会超过还缺的
体积,则停止搭建(可行性剪枝)
剪枝4:搭建过程中发现还没搭的那些层的体积,最大也到不了还缺
的体积,则停止搭建(可行性剪枝)

假设构建2层蛋糕,最低层为r1,h1,第二层为r2,h2,公式如下:
V=Nπ=πr1平方h1 + πr2平方h2,N=r1平方h1 + r2平方h2
同理:蛋糕表面积=蛋糕最底层蛋糕的圆面积 + 每层蛋糕的侧面积
因为每层蛋糕的顶面积刚好就是最底层蛋糕的圆面积
Q=Sπ= πr1平方 + 2πr1h1 + 2πr2h2,提取π公因子
S = r1平方 + 2r1h1 + 2r2h2

python 代码实现:
 1 import math
 2 
 3 # N-蛋糕的总体积,M-层数
 4 N, M = 0, 0
 5 # 最优表面积,初值为无穷大
 6 minArea = float("inf")
 7 # 正在搭建中的蛋糕的表面积
 8 area = 0
 9 # 统计循环的次数
10 num = 0
11 # 当前体积
12 currentVolume = 0
13 
14 # 要用m层去凑体积v,最底层半径不能超过r,高度不能超过h
15 # 求出最小表面积放入minArea
16 def Dfs(v, m, r, h):
17     global N, M, minArea, area, num, currentVolume
18     # m=0,v=0时,说明蛋糕搭建成功,因为的递减,m=0说明不需要搭层数了
19     # 而且v=0,说明体积大小也搭建符合题目的要求了,v!=0,说明已经搭到
20     # 顶层蛋糕了,但是体积没有搭到输入N的要求,所以失败了
21     if m == 0:
22         if v == 0:
23             # 找到一个最优值与之前的最优值进行比较,如果更小则替换
24             minArea = min(minArea, area)
25             return
26         else:
27             return
28     # 做m层蛋糕,但是体积v是0或者小于0是做不到的
29     if v <= 0:
30         return
31     # 半径i从最大半径r开始依次递减1,直到m,因为如果要做m层蛋糕,
32     # 最底层的蛋糕的半径最小值为m,同理最底层蛋糕的高度最小值也为m
33     # 因为题目要求是整数,而且逐层半径、高度最小相差1
34     for i in range(r, m-1, -1):
35         # S = r1平方+各层蛋糕的侧面积
36         # 蛋糕的底面积
37         if m == M:
38             area = i * i
39         for j in range(h, m-1, -1):
40             # currentVolume表示搭好的当前蛋糕的体积
41             currentVolume = currentVolume + (i*i*j)
42             # 剪枝:如果按此半径和高度,搭好的蛋糕的体积超过输入的N,则该方案不可行
43             if currentVolume > N:
44                 # 深度遍历需要回溯,之前加过这个值了,但是该条路径走不通,因此要把之前加的值减回去
45                 currentVolume -= (i * i * j)
46                 continue
47             area += 2 * i * j
48             # 剪枝:如果计算出正在搭建的表面积已经大于曾经计算出的最优表面积
49             # 则不需要再往下搭建了,即使能搭建出来也不是最优的表面积
50             if area > minArea:
51                 # 该条路径走不通,因此要把之前加的值减回去
52                 area -= 2 * i * j
53                 continue
54             num += 1
55             Dfs(v-i*i*j, m-1, i-1, j-1)
56             # 走过的路重新往回走,需要把原来增加的值减回去,以保证下次新的走法走到该点时,
57             # area、currentVolume的值是与原初始化的值
58             area -= 2 * i * j
59             currentVolume -= (i * i * j)
60 
61 
62 def main():
63     global N, M, minArea, num
64     N, M = map(int, input().split())
65     # 这里对半径和高度都是做个初略的假设
66     # 最大半径是假设蛋糕就1层,高度是1的情况,根据公式,可以对N开根号即可得最大值
67     # 最大高度是假设蛋糕的半径是1的情况,N就是高度的最大值
68     Dfs(N, M, int(math.sqrt(N)), N)
69     if minArea == float("inf"):
70         print("%d" % 0)
71     else:
72         print("S = %d,循环次数:%d" % (minArea, num))
73 
74 
75 if __name__ == '__main__':
76     main()


原文地址:https://www.cnblogs.com/an-wl/p/13282675.html