题目链接:https://www.acwing.com/problem/content/description/170/
题目:
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π。
第二行为整数 M,表示蛋糕的层数为 M。
输出格式
输出仅一行,是一个正整数 S(若无解则 S=0)。
数据范围
1≤N≤10000,
1≤M≤20输入样例:
100 2
输出样例:
68
分析:
通过搜索的方式,我们枚举每一层的r 和 h ,求出其中表面积最小的情况。
1.优化搜索顺序剪枝: 为了让分支数尽量少,所以我们每次枚举都使用尽可能大的值。
而根据题意,下面一层的蛋糕的高 和 半径 都要严格大于上面一层的蛋糕。
所以我们从底往上枚举,同时先枚举R在枚举H。(因为体积用到的R为R^2,H只为H)
并且值从大到小进行枚举。
2.由于下面一层严格大于上面一层,所以从最高一层视为第一层的话,则第u层的半径r >= u , 高度h >= u . 同时 r < r[u + 1] , h < h[u + 1]
同时还存在一个限制条件。
由于我们DFS的时候,是从下往上的,所以r[u + 1],h[u + 1], 当前消耗的体积v都是已经求出来了的。
所以当前第u层: r^2 * h <= n - v.
h最小也要为1,则 r <= sqrt(n - v), h <= (n - v) / r ^ 2
3. 初始化前n层所需要的最小值。
由于dfs从底向上枚举,所以当前的s[u ~ end] + mins[1 ~ u - 1] < ans。 因为当前的s[u ~ end]已经确定,而最小满足情况下的mins最后的和都要 >= ans的话,则不用 在继续枚举了,直接return即可。
v[u ~ end] + minv[1 ~ u -1] <= n才行,同上面的,如果加上了最小值都大于 n 的话,就必定不满足,直接return即可。
4.
s[1 ~ u] = 2 * r[k] * h[k] (k从1~u)
v[1 ~ u] = n - v[u + 1 ~end] = r [k]* r[k] * h[k];
而这两个有一个什么样的关系呢?
s[1 ~ u] = 2 * r[k] * h[k] (k从1~u) = 2 / r[u + 1] * r[k] * h[k] * r[u + 1] (k从1~u)
>= 2 / r[u + 1] * r[k] * h[k] * r[k] >= 2 / r[u + 1] * v[1 ~ u ]
其中为什么是大于等于呢?而不是大于呢?r[u + 1]不是必定大于r[k ]的嘛?
这是因为存在一个边界情况,就是当k 为最后一层的时候,
实际上就变为了 0 >= 0了。
所以为 >= 而不是 >
而这个有什么用呢?
s[1 ~ u] >= 2 / r[u + 1] * v[1 ~ u].
那么就向上面剪枝的时候需要考虑的一样,s[u + 1 ~ end] + s[1 ~ u] < ans才会继续向下枚举
也就是s(s的含义是s[u + 1 ~ end]) + 2 / r[u + 1] * v[1 ~ u] >= ans 直接return,不用向下枚举了。
代码实现:
# include <iostream>
# include <cmath>
using namespace std;
const int M = 25;
int minv[M];//minv[i], 1~i层的最小体积和 (高为1,R为1, 高为2,R为2....)
int mins[M]; // 其中这个mins考虑的是侧面积,不考虑顶层的面积,//mins[i],1 ~ i 层的最小面积和(高为1,R为1, 高为2,R为2....)
int n,m;
int ans = 0x3f3f3f3f;
int r[M],h[M];
void dfs(int u , int v , int s) // 当前已经弄完了 u + 1 到 m层,还剩下u层,现在已经用去了体积v,表面积已经达到了s
{
if(u == 0) // 还剩下0层,也就是弄好了
{
if(v == n)
{
ans = min(s,ans);
}
return;
}
// 最优性剪枝
if(s >= ans)
{
return;
}
// 可行性剪枝3
if(s + mins[u] >= ans)
{
return;
}
if(v + minv[u] > n)
{
return;
}
// 可行性剪枝4
if(s + 2 * (n - v) / r[u + 1] >= ans)
{
return;
}
// 可行性剪枝2 + 剪枝1 从大到小是优化搜索顺序
for(int i = min(r[u + 1] - 1 , (int)sqrt(n - v)) ; i >= u ; i--)
{
for(int j = min(h[u + 1] - 1 , ( n - v) / i / i ) ; j >= u ; j--)
{
r[u] = i, h[u] = j;
if(v + i * i * j > n)
{
continue;
}
if(u == m) // 当前为最下面那一层,加上 上表面积
{
dfs(u - 1, v + i * i * j , s + 2 * i * j + i * i);
}
else
{
dfs(u - 1, v + i * i * j , s + 2 * i * j );
}
}
}
}
int main()
{
scanf("%d %d",&n,&m);
for(int i = 1 ; i <= m ; i++)
{
minv[i] = minv[i - 1] + i * i * i; // r^2 * h;
mins[i] = mins[i - 1] + 2 * i * i;
}
r[m + 1] = 0x3f3f3f3f;
h[m + 1] = 0x3f3f3f3f;
dfs(m,0,0);
if(ans == 0x3f3f3f3f)
{
ans = 0;
}
printf("%d\n",ans);
return 0;
}