生日蛋糕
描述
7月17日是Mr.W的生日,ACM-THU爲此要製作一個體積爲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
難度
極高,深搜,動態規劃
解法
首先,整個過程不需要考慮Pi
給出部分公式
圓柱體積 V = r * r * h
圓柱側面積 S = 2 * r * h
圓柱底面積 C = r * r
蛋糕的層數N (N <= 20) ,蛋糕體積V(<=100000)
意味着蛋糕塔的高度H的最小值是N
可以用遞推公式算出第i層到第N層蛋糕圓柱側面積的最小值之和
可以用遞推公式算出第i層到第N層蛋糕圓柱體積的最小值之和
需要估計R和H的上屆
根據以上的計算結果,可以估算出底面積半徑的最大值MaxR
根據以上的計算結果,可以估算出最底層的最大高度值MaxH
最底層圓柱的區間 H = 【1 ,maxH】
最底層圓柱的底半徑 N =【N,maxR】
深搜
由於要計算表面積(露出來的),那麼在一層一層的計算過程中,如果最下面的一層確定了,則上表面積就確定了,所以搜索框架從下到上。
深度優先搜索的搜索目標:枚舉每一層可能的高度和半徑,找到可行方案,記錄圓柱體的表面積之和
搜索面對的狀態有:第幾層,半徑和高度,剩餘體積,累計表面積
搜索和枚舉的順序? 從大開始遞減枚舉 從底層往上搭,從N開始遞歸搜索
剪枝
啓發式剪枝:
預估此路徑剩餘體積的最小表面積值,若超過全局變量的最小值,則無需繼續搜索
可行性剪枝:
剩餘體積小於ifloor的最小體積,體積餘量不足,無需繼續搜索
可行性剪枝+啓發性剪枝 :
累計表面積+最小表面積>全局最小表面積,無需繼續搜索
代碼
//code by Andy
#include <bits/stdc++.h>
using namespace std;
#define INF 0x7fffffff
#define ButtonArea(r) (r*r) //底面積
#define surArea(r,h) (2*r*h) //側面積
#define Volume(r,h) (r*r*h) //體積
#define V2surArea(r,v) (2*v/r) //根據體積和半徑計算側面積
int V,N,minsurArea = INF;
//遞推計算各層的體積與側面積區間下界
int sumMinS[27],sumMinV[27];
//第幾層、半徑、高度、剩餘體積、累計表面積
void dfs0(int ifloor,int preR,int preH,int leftV,int surArea)
{
if(ifloor==0){
//唯有剩餘體積剛好爲0,此路徑有解,若累計面積較小就記錄全局變量
if(leftV==0 && surArea<minsurArea)
minsurArea = surArea;
return;
}
//啓發式剪枝,預估此路徑剩餘體積的最小表面積值,若超過全局變量的最小值,則無需繼續搜索
if(preR>1 && V2surArea(preR-1,leftV)+surArea >=minsurArea) return;
//可行性剪枝
if(leftV<sumMinV[ifloor]) return;
//可行性剪枝+啓發性剪枝 累計表面積+最小表面積>全局最小表面積
if(surArea+sumMinS[ifloor]>=minsurArea) return;
//枚舉所有的R和H,深搜
for(int r=preR-1;r>=ifloor;r--){
if(ifloor==N) surArea = ButtonArea(r); //最底層面積
//確定高度枚舉範圍上界
int H_max = 1.0*leftV/ButtonArea(r)+1; //剩餘體積除於底面積爲高
if(H_max>preH-1) H_max=preH-1; //高的最大值
for(int h=H_max;h>=ifloor;h--)
dfs0(ifloor-1,r,h,leftV-Volume(r,h),surArea+surArea(r,h));
}
}
int main() {
cin>>V>>N;
//從頂層往下遞推計算,最高層爲0
sumMinS[0]=sumMinV[0]=0;
for(int i=1;i<=N;i++){
//所有半徑和高度都是正整數,所以可得下面遞推式
sumMinS[i] = sumMinS[i-1] + surArea(i,i);
sumMinV[i] = sumMinV[i-1] + Volume(i,i);
}
//最底層最大的H和R
//最頂層體積最小,最底層半徑最大爲N
int maxH = (V-sumMinV[N-1])/ButtonArea(N)+1;
//高度爲1時圓柱半徑最大
int maxR = sqrt(double((V-sumMinV[N-1])+1));
minsurArea = INF; //將minsubArea置成一個很大的值
dfs0(N,maxR,maxH,V,0);
if(minsurArea==INF)
cout<<0<<endl;
else
cout<<minsurArea<<endl;
return 0;
}
小結
需要找到一種方案從頭至尾的最優解需要使用深搜,與動態規劃,貪心算法不同的是,採用深度優先搜索求最優解的問題不具有最優子性質。而在深搜的過程中,複雜度較高,需要進行適當的剪枝降低時間複雜度。主要的剪枝方案有可行性剪枝和啓發性剪枝。