算法實踐:生日蛋糕

生日蛋糕

描述

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;
}

小結

需要找到一種方案從頭至尾的最優解需要使用深搜,與動態規劃,貪心算法不同的是,採用深度優先搜索求最優解的問題不具有最優子性質。而在深搜的過程中,複雜度較高,需要進行適當的剪枝降低時間複雜度。主要的剪枝方案有可行性剪枝和啓發性剪枝。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章