You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

67 lines
2.9 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 22;
int n; // 待制作的蛋糕的体积为
int m; // 蛋糕的层数为 m
int ans = INF; // 最小表面积,预求最小,先设最大
int mins[N]; // 每一层的最小表面积
int minv[N]; // 每一层的最小体积
int R[N], H[N]; // 为每一层蛋糕分配的半径和高度
// u:正在研究第几层
// v:已经使用了的体积
// s:已经使用了的表面积
void dfs(int u, int v, int s) {
// 如果来到第u层发现已经使用过的体积v,加上第1~u层最小体积minv[u] ,大于整体的体积n就不用再继续了
if (v + minv[u] > n) return;
// 如果来到第u层发现已经使用过的表面各s,加上第u层及以上所有层需要的表面积mins[u]
// 大于当前最优解了,就不用再继续研究了,还不如最优解,再研究也是白扯
if (s + mins[u] >= ans) return;
// 第二个最优性剪枝,侧面积和体积不是孤立存在的,是有关联关系的。
// 不符合这个关联关系的Ri和Hi是没有必要继续的
if (2 * (n - v) / R[u + 1] + s >= ans) return;
// 摆完所有层蛋糕
if (u == 0) {
// 如果已经使用的体积等于n如果是那么更新答案
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--) {
// 如果是第m层需要加上一个底面积其它层就只计算侧面积
int t = (u == m ? r * r : 0);
// 记录下第u层的选择,半径r,高度h
R[u] = r, H[u] = h; // 因为静态数组可以重复按索引写入,所以不需要回溯
// 递归上一层,体积变为v+r*r*h,表面积由s变大为 s + 2 * r * h + t
dfs(u - 1, v + r * r * h, s + 2 * r * h + t);
}
}
int main() {
cin >> n >> m;
// 预处理
for (int i = 1; i <= m; i++) {
// 第i层半径最小是i,高度最小是i,否则更小层无法获得整数解
// 预处理出每层及上边所有层的最小表面积,看清楚这是一个叠加的关系,不是简单的计算,是递推式
mins[i] = mins[i - 1] + 2 * i * i;
// 预处理出每层及上边所有层的最小体积
minv[i] = minv[i - 1] + i * i * i;
}
// 初始化为无穷大,防止在计算下层半径高度时取到0
R[m + 1] = H[m + 1] = INF;
// 从下向上进行深搜,按经验来看,这样能减少出发时的分枝数量,效率提升
// 从m层出发此时的体积为0此时的表面积为0
dfs(m, 0, 0);
// 输出答案
if (ans == INF)
puts("0");
else
printf("%d\n", ans);
return 0;
}