算法实践:生日蛋糕

生日蛋糕

描述

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】

扫描二维码关注公众号,回复: 11068744 查看本文章

最底层圆柱的底半径 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;
}

小结

需要找到一种方案从头至尾的最优解需要使用深搜,与动态规划,贪心算法不同的是,采用深度优先搜索求最优解的问题不具有最优子性质。而在深搜的过程中,复杂度较高,需要进行适当的剪枝降低时间复杂度。主要的剪枝方案有可行性剪枝和启发性剪枝。

发布了145 篇原创文章 · 获赞 194 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/matafeiyanll/article/details/105404034