深搜的剪枝技巧(二)——生日蛋糕(优化搜索顺序、可行性剪枝,最优性剪枝)
生日蛋糕(优化搜索顺序、可行性剪枝,最优性剪枝)
-
问题描述
- Mr. W 要制作一个体积为 \(N\pi\) 的 M 层生日蛋糕,每层都是一个圆柱体,设从下往上数第 i (\(1\leq i \leq M\)) 层蛋糕是半径为 \(R_i\),高度为 \(H_i\) 的圆柱。当 \(i < M\) 时,要求 \(R_i > R_{i+1}\),且 \(H_i > H_{i+1}\)。由于要在蛋糕上抹奶油,为尽可能节约经费,我们希望蛋糕外表面(最下一层的下底面除外)的面积 Q 最小。令 \(Q = S\pi\),请对给出的 N 和 M 编程,找出蛋糕的制作方案(适当的 \(R_i\) 和 \(H_i\) 的值),使 S 最小(除 Q 外,以上所有数据皆为正整数)
-
输入格式
- 第一行为 N (\(N \leq 10000\))。表示待制作的蛋糕体积为 \(N\pi\)
- 第二行为 M (\(M\leq 20\)),表示蛋糕的层数为 M
-
输出格式
- 输出仅一行,是一个整数 S (若无解则 S = 0)
-
样例输入
100 2
-
样例输出
68
附:圆柱公式:体积 \(V = \pi R^2H\);侧面积:\(A^{'} = 2\pi RH\);底面积:\(A = \pi R^2\)
-
思路分析——枚举每一层可能的高度和半径。确定搜索范围,即底层蛋糕的最大可能半径和最大可能高度。确定搜索顺序,从底层往上搭蛋糕。
-
剪枝思路
- 可行性剪枝 1 ——搭建过程中预见到再往上搭,高度已无法安排,或者半径已经无法安排,则停止搭建
- 可行性剪枝 2 ——搭建过程中发现还没搭的那些层的体积,一定会超过还缺的体积,则停止搭建
- 可行性剪枝 3 ——搭建过程中发现还没搭的那些层的体积,最大也到不了还缺的体积,则停止搭建
- 最优性剪枝——搭建过程中发现已建好的面积已经超过目前求得的最优表面积,或者预见到搭建完后面积一定会超过目前最优表面积,则停止搭建
-
代码(未剪枝,交上去会超时)
#include <iostream> #include <cmath> using namespace std; int N,M; int minArea = 1 << 30; int area = 0; void Dfs(int v,int n,int r,int h); int main() { cin>>N>>M; int MaxR = sqrt(N); //粗略估计,忽略 π int MaxH = N; Dfs(N,M,MaxR,MaxH); if(minArea == 1 << 30) cout<< 0 <<endl; else cout<< minArea <<endl; return 0; } void Dfs(int v,int n,int r,int h) { if(n == 0) { if(v) return; else { minArea = min(minArea,area); return; } } if(v <= 0) return; for(int rr = r; rr >= n; --rr) { if(n == M) //底面积 area = rr * rr; for(int hh = h; hh >= n; --hh) { area += 2 * rr * hh; Dfs(v-rr*rr*hh,n-1,rr-1,hh-1); area -= 2 * rr * hh; } } }
-
代码(剪枝修改过的)
#include <iostream> #include <cmath> using namespace std; int N,M; int minArea = 1 << 30; int minA[22] = {0},minV[22] = {0}; int area = 0; int maxVJudge(int m, int r, int h); void Dfs(int v,int n,int r,int h); int main() { cin>>N>>M; for(int i = 1; i <= M; i++) { minA[i] = minA[i-1] + 2 * i * i; minV[i] = minV[i-1] + i * i * i; } int MaxH = (N - minV[M-1]) / (M * M) + 1;//最大的高度 int MaxR = sqrt((N - minV[M-1]) / M) + 1;//最大的体积 Dfs(N,M,MaxR,MaxH); if(minArea == 1 << 30) cout<< 0 <<endl; else cout<< minArea <<endl; return 0; } int maxVJudge(int m, int r, int h) { int maxV = 0; for(int i = 0; i < m; i++) maxV += (r-i) * (r-i) *(h-i); return maxV; } void Dfs(int v,int n,int r,int h) { if(n == 0) { if(v) return; else { minArea = min(minArea,area); return; } } if(v<= 0) // 可行性剪枝1 return ; if(minV[n] > v)//可行性剪枝2 return ; if(maxVJudge(n,r,h) < v)//可行性剪枝3 return ; if(minA[n]+area >= minArea) // 最优性剪枝 return; for(int rr = r; rr >= n; --rr) { if(n == M) //底面积 area = rr * rr; for(int hh = h; hh >= n; --hh) { area += 2 * rr * hh; Dfs(v-rr*rr*hh,n-1,rr-1,hh-1); area -= 2 * rr * hh; } } }