描述
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
提示:
圆柱公式
体积V = πR2H
侧面积A’ = 2πRH
底面积A = πR2
分析
算出半径r与高度h的取值范围,由底而上构建搜索蛋糕的组成,并且先算出构建到第i层时前面所有层数蛋糕的最小表面积与最小体积,进行适当剪枝,更详细的请看注释。
实现
#include <iostream>
#include <cstring>
#include <cmath>
using namespace std;
int minA[21]; //n层蛋糕最小表面积
int minV[21]; //n层蛋糕最小体积
int minArea = 1 << 30; //最小表面积
int area = 0; //当前表面积
int getCurrentMaxV(int n, int maxR, int maxH)
{
int maxNV = 0;
for (int i = 0; i < n; i++) {
maxNV += (maxR - i) * (maxR - i) * (maxH - i);
}
return maxNV;
}
void dfs(int maxM, int v, int i, int maxR, int maxH)
{
if (i == 0) {
if (v == 0) {
minArea = minArea > area ? area : minArea;
}
//不管搭成还是没搭成都返回。
return;
}
//第一层最小r=h=1, 所以r与h都得不得小于层数i。
if (v <= 0 || minV[i] > v || maxR < i || maxH < i) return;
//minA[i]为第1-i层的体积,area为第i+1-m层的体积,相当于搭建完成的总体积要大于或等于已搜索到的最小体积。
if (minA[i] + area >= minArea) return;
//如果剩下的i层能凑出的最大体积maxNV比要凑出的v小的话则不必再搜索。
if (getCurrentMaxV(i, maxR, maxH) < v) return;
//由大到小,缩小可选择范围。
for (int r = maxR; r >= i; r--) {
if (i == maxM) {
//要求的最小表面积需是每层蛋糕的侧面积加上两层蛋糕单相差出来的上面部分的表面积,1-maXM层蛋糕上面部分的表面积
//加起来便是第max层层蛋糕的底面积。
area = r * r;
}
for (int h = maxH; h >= i; h--) {
area += 2 * r * h;
dfs(maxM, (v - r * r * h), i - 1, r - 1, h - 1);
area -= 2 * r * h;
}
}
}
int main()
{
// freopen("in.txt", "r", stdin);
int n, m;
cin >> n >> m;
minV[0] = minA[0] = 0;
//题目中是举例说从下往上数第i层蛋糕,但是在这里我们是按从上往下数算第i层的。
for (int i = 1; i <= m; i++) {
//半径最小是i,高度最小是i,π全都扔掉不乘。
minV[i] = minV[i - 1] + i * i * i;
minA[i] = minA[i - 1] + 2 * i * i;
}
//最小体积已超过n,不可能搭成。
if (minV[m] > n) {
cout << 0 << endl;
return 0;
}
//最大高度等于最下面一层的最大体积除以最小表面积,为除以小数部分再加1,最大半径求法类似。
int maxH = (n - minV[m - 1]) / (m * m) + 1;
int maxR = sqrt(double(n - minV[m - 1]) / m) + 1;
dfs(m, n, m, maxR, maxH);
cout << (minArea == 1 << 30 ? 0 : minArea) << endl;
return 0;
}