生日蛋糕--C++

前言:这是算法中很经典的一道题

题目:7月17日是Mr.W的生日,ACM-THU为此要制作一个体积为NπNπ的M层生日蛋糕,每层都是一个圆柱体。

设从下往上数第i层蛋糕是半径为RiRi, 高度为HiHi的圆柱。

当i < M时,要求RiRi > RiRi+1且HiHi > HiHi+1。

由于要在蛋糕上抹奶油,为尽可能节约经费,我们希望蛋糕外表面(最下一层的下底面除外)的面积Q最小。

令Q = Sπ ,请编程对给出的N和M,找出蛋糕的制作方案(适当的RiRi和HiHi的值),使S最小。

除Q外,以上所有数据皆为正整数 。

输入格式

输入包含两行,第一行为整数N(N <= 10000),表示待制作的蛋糕的体积为NπNπ。

第二行为整数M(M <= 20),表示蛋糕的层数为M。

输出格式

输出仅一行,是一个正整数S(若无解则S = 0)。

数据范围

1≤N≤100001≤N≤10000,
1≤M≤201≤M≤20

输入样例:

100
2

输出样例:

68

题解思路:

该题是经典的爆搜剪枝问题,要求体积不变下,表面积最小(除了第一层的底面积) ,那么从每一层蛋糕的角度来看,蛋糕的半径和高度都是单调递减,从枚举每一层的半径和高度,枚举的时候有一个原则,即优先枚举 选择决策少的来扩展,半径和高度这两个变量搜索范围的确定有点意思,后面会讲解。

关于爆搜剪枝问题,剪枝当然是重中之重,该题可以提出四个剪枝方法;剪枝经常应用于深搜和广搜当中,策略就是寻找过滤条件,提前减少不必要的搜索路径。

搜索解的讨论(搜索极其重视搜索顺序,个头大的先搜索,方案数将较少):1、贪心的思想是保持每步最优解,以达到全局最优解2、爆搜如何产生最优解呢?将需要搜索顺序排成从大到小,从中进行搜索。

从蛋糕从下往上搜索,上表面积就是最下面圆的面积,求解过程注重侧面积和体积即可,

假设当前状态有 s,v,rdep+1∼m,hdep+1∼ms,v,rdep+1∼m,hdep+1∼m, dep对应的状态,有:

n−v≥r2dephdep
n−v≥rdep2hdep
有上界:

rdep=min(n−v,rdep+1−1)
rdep=min(n−v,rdep+1−1)
hdep=min(n−vr2dep,hdep+1−1)
hdep=min(n−vrdep2,hdep+1−1)
有下界:

rdep=dep,hdep=dep
rdep=dep,hdep=dep
很明显,ri=i,hi=iri=i,hi=i 是最小的情况,

它们对应的 minsi=2rihi=2i2,minvi=r2ihi=i3minsi=2rihi=2i2,minvi=ri2hi=i3
如果当前结果加最小情况超过答案,那么后面就不会有解。

在有解的情况下,有下面不等式成立:

s1∼dep−1=2∑i=1dep−1rihi,v1∼dep−1=n−v=∑i=1dep−1r2ihi
s1∼dep−1=2∑i=1dep−1rihi,v1∼dep−1=n−v=∑i=1dep−1ri2hi
ri<rdep,1≤i≤dep−1
ri<rdep,1≤i≤dep−1
s1∼dep−1=2∑i=1dep−1rihi=2rdep∑i=1dep−1rihirdep≥2rdep∑i=1dep−1r2ihi
s1∼dep−1=2∑i=1dep−1rihi=2rdep∑i=1dep−1rihirdep≥2rdep∑i=1dep−1ri2hi
s1∼dep−1≥2(n−v)rdep
s1∼dep−1≥2(n−v)rdep
即,2(n−v)rdep2(n−v)rdep 是后面未求面积的下限,如果 2(n−v)rdep+s2(n−v)rdep+s 大于答案,那么这样的状态是不会更优的。

作者:VMice
链接:https://www.acwing.com/solution/AcWing/content/1500/

#include<iostream>
#include<cmath>
#include<algorithm>

using namespace  std;

const int N=25,Inf=1e9;
//全局变量
int n,m;
int R[N],H[N];//每层的半径和高度
int minv[N],mins[N];
int res=Inf ; //定义初始答案为正无穷
/*
爆搜:对每层蛋糕的半径和高度进行搜索,注意更新半径和高度
*/
void dfs(int u,int v,int s)//args:当前层数、当前体积、当前表面积
{
     //四个剪枝方法
    if(v+minv[u]>n)return;
    if(s+mins[u]>=res)return;
    //最重要的优化
    if(s+2*(n-v)/R[u+1]>=res)return;
    
    if(!u)  //如果到达最上一层,输出最上一层,非常重要的一个语句,目的是使爆搜达到条件后终止
    {
        if(n==v)res=s;//在最后一层搜索时,主要的操作步骤是找到满足条件的解后结束
        return;
    }
    //终于理解了为什么第u层的r,h最小半径为u,因为最顶层r,h最小为1,那么依次递增,最大层的m的r,h最小值为m
    for(int r=min((int)(double)sqrt(n-v),R[u+1]-1);r>=u;r--)//枚举状态,r,h从大到小枚举,同时有范围
        for(int h=min((n-v)/r/r,H[u+1]-1);h>=u;h--) //
            { 
                //注意当最后一层确定时,要加第一层的低盘面积
                int t=0;
                if(u==m)t=r*r;
                R[u]=r,H[u]=h;   //新知识点:更新当前的半径和高度
                dfs(u-1,v+r*r*h,s+2*r*h+t);//参数更新及传递
            }
}
int main()
{
    
   //读入数据
    cin>>n>>m;
    //初始化minv,mins
    for(int i=1;i<=m;i++)
    {
       // minv[i+1]=minv[i]+i*i*i;
       //mins[i+1]=minv[i]+2*i*i;
        minv[i]=minv[i-1]+i*i*i;
        mins[i]=mins[i-1]+2*i*i;
    }
    //处理搜索边界#这里设置哨兵,帮助完成边界
    R[m+1]=H[m+1]=Inf;//?
    dfs(m,0,0);//赋初值最底下一层,初始面积为0,初始表面积为0
    cout<<res<<endl;
    for(int i=0;i<m;i++)
    {
        cout<<minv[i]<<" ";
        cout<<"\n";
        cout<<mins[i]<<" ";
    }
    return 0;
}

发布了21 篇原创文章 · 获赞 8 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/weixin_40405758/article/details/96604123
今日推荐