AcWing 168 生日蛋糕

题目描述:

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

设从下往上数第i层蛋糕是半径为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)。

数据范围

1≤N≤10000,
1≤M≤20

输入样例:

100
2

输出样例:

68

分析:

本题虽然看起来比较复杂,但是只要稍加分析,思路还是很清晰的。将自上而下的各层蛋糕编号为1-m,题目要求我们设计一个m层蛋糕,下层的蛋糕比上层的蛋糕半径和高度都要大,并且总的体积为n,求最小的表面积。设第i层蛋糕的半径是Ri,高度是Hi,半径和高度都是整数。有Ri < Ri+1,Hi < Hi+1,根据题目要求,我们下面先忽略体积和表面积公式中的π。由于最上层的蛋糕半径和高度都要是正整数,所以R1和H1至少是1,R2要大于R1,至少是2,由此可得,第i层蛋糕的半径和高度至少是i。蛋糕体积公式为sum(Ri*Ri*Hi),表面积公式为Rm*Rm + sum(2Ri*Hi),也就是最下层蛋糕的底面积加上各层蛋糕的侧面积,这里有个简单的推导,第m层的上表面积是Sm - Sm-1,第m-1层的上表面积是Sm-1 - Sm-2,...,第1层的上表面积是S1,加起来得到上表面积的总和就是Sm = Rm*Rm。

考虑下剪枝,首先是优化搜索顺序,为了先搜索的分支数少,我们应该自大到小搜索,也就是从第m层搜索到第1层,并且体积公式中Ri是平方级别的,变化率要高于Hi,所以先搜Ri,再搜Hi,对于半径和高度的枚举也应该是自大到小的。下面考虑各层高度和半径的范围,前面说到,第i层的高度和半径至少是i,这就是各层高度和半径的最小值,并且Ri < Ri+1,Hi < Hi+1,这是其中的一个上界。设mins[i]表示1到i层表面积的最小值,minv[i]表示1到i层体积的最小值,则mins[i] = mins[i-1] + 2* i * i,因为第i层高度和半径都至少是i,minv[i] = minv[i-1] + i * i * i。我们从第m层蛋糕搜到当前的第u层时,设已经使用的表面积是s,体积是v,则从第u层到第1层留给我们的体积最多只有n - v了,所以第u层的半径Ru*Ru*Hu <= n - v,Hu至少是u,所以Ru <= sqrt((n-v)/u),当然,算法竞赛进阶指南上面的公式这里没有除以u,因为把H看作最小是1了,对结果影响不大。同样,Hu <= (n-v) / v / v,这就是Hu和Ru的另一个上界,上界取两个上界的最小值即可。

可行性剪枝:前面说到从第m层搜到第u层总的体积是v,而从第1层到第u层体积至少是minv[u],所以当v + minv[u] > n时就说明超过了给定的体积n,方案不合法。

最优性剪枝:第一个最优性剪枝是s + mins[u] >= ans时,这里ans是之前搜到的最小表面积,也就是当这个方案搜下去的表面积不可能优于小于之前搜到的解时,就应该否定这个方案。还有第二个最优性剪枝,考虑下从第1层到第k层的体积和表面积的表达式,S = sum(2*Ri*Hi),V = sum(Ri*Ri*Hi),则S = sum(2*Ri*Hi*Rk+1 / Rk+1) > 1 / Rk+1 * sum(2*Ri*Ri*Hi ) = 2 / Rk+1 * sum(Ri*Ri*Hi) = 2*V / Rk+1,sum表示i从1到k求和。从第m层到第u层已有的表面积是s,从第1层到第u层的表面积一定超过2*V/Ru+1,从第1层到第u层剩下的体积是n - v,所以当s + 2*(n-v)/ Ru+1  >= ans时,s + S > ans,一定不是最优解,所以可以剪枝。

最后递归的边界是u == 0,遍历完了所有层蛋糕,当v恰好是n时,就可以更新最优解了。

#include <iostream>
#include <cmath>
using namespace std;
const int N = 22;
int n,m,ans = 1e9;
int mins[N],minv[N],R[N],H[N];
void dfs(int u,int v,int s){
    if(v + minv[u] > n) return;
    if(s + mins[u] >= ans)   return;
    if(2*(n-v)/R[u+1] + s >= ans)   return;
    if(!u){
        if(v == n)  ans = s;
        return;
    }
    for(int r = min(R[u+1]-1,(int)sqrt((n-v) / u));r>=u;r--){
        for(int h = min(H[u+1]-1,(n-v)/r/r);h>=u;h--){
            int t = (u == m ? r * r : 0);
            R[u] = r,H[u] = h;
            dfs(u - 1,v + r * r * h,s + 2 * r * h + t);
        }
    }
}
int main(){
    cin>>n>>m;
    for(int i = 1;i <= m;i++){
        mins[i] = mins[i-1] + 2 * i * i;
        minv[i] = minv[i-1] + i * i * i;
    }
    R[m + 1] = H[m + 1] = 1e9;
    dfs(m,0,0);
    if(ans == 1e9)  cout<<"0"<<endl;
    else    cout<<ans<<endl;
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值