信奥赛C++提高组csp-s之搜索进阶(搜索剪枝案例实践2)

信奥赛C++提高组csp-s之搜索进阶(搜索剪枝案例实践2)

生日蛋糕 ------ 上下界剪枝 + 最优性剪枝

题目描述

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

设从下往上数第 i i i( 1 ≤ i ≤ M 1 \leq i \leq M 1≤i≤M)层蛋糕是半径为 R i R_i Ri,高度为 H i H_i Hi 的圆柱。当 i < M i \lt M i<M 时,要求 R i > R i + 1 R_i \gt R_{i+1} Ri>Ri+1 且 H i > H i + 1 H_i \gt H_{i+1} Hi>Hi+1。

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

请编程对给出的 N N N 和 M M M,找出蛋糕的制作方案(适当的 R i R_i Ri 和 H i H_i Hi 的值),使 S = Q π S=\dfrac{Q}{\pi} S=πQ 最小。

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

输入格式

第一行为一个整数 N N N( N ≤ 2 × 10 4 N \leq 2 \times 10^4 N≤2×104),表示待制作的蛋糕的体积为 N π N\pi Nπ。

第二行为 M M M( M ≤ 15 M \leq 15 M≤15),表示蛋糕的层数为 M M M。

输出格式

输出一个整数 S S S,若无解,输出 0 0 0。

输入输出样例 1
输入 1
复制代码
100
2
输出 1
复制代码
68

思路分析

问题理解

我们需要制作一个 M 层 的圆柱形蛋糕,从下往上每层的半径 R i R_i Ri和高度 H i H_i Hi 均为正整数,且满足 R i > R i + 1 , H i > H i + 1 R_i > R_{i+1},\ H_i > H_{i+1} Ri>Ri+1, Hi>Hi+1。蛋糕总体积为 N π N\pi Nπ,要求外表面面积 Q 最小(最下一层的下底面除外)。输出 S = Q / π S = Q / \pi S=Q/π(整数)。

外表面面积计算方式为:所有层的侧面积之和 + 最底层的下底面积

解题思路

由于 M ≤ 15 M \le 15 M≤15, N ≤ 20000 N \le 20000 N≤20000,直接枚举所有可能的半径和高度组合会爆炸,必须使用DFS + 剪枝

1. 搜索顺序

从**最底层(第 M 层)开始向最顶层(第 1 层)**搜索。因为底层半径和高度较大,先枚举大值有利于后续剪枝。

2. 状态表示
  • dep:当前正在处理的层数(从 M 到 1)
  • v:已经使用的体积
  • s:已经累积的表面积(注意:当 dep == M 时会加上最底层的下底面积)
3. 预处理最小可能值(剪枝用)
  • minv[i]:从上往下前 i 层的最小体积(假设每层半径 = 高 = i,则体积为 i 3 i^3 i3)
  • mins[i]:前 i 层的最小侧面积(每层侧面积 2 π r h 2\pi r h 2πrh,最小为 2 i 2 2i^2 2i2)
4. 剪枝策略
  • 体积剪枝:若当前体积 + 剩余层的最小体积 > N,则不可能完成,剪枝。
  • 表面积剪枝:若当前表面积 + 剩余层的最小侧面积 ≥ 当前最优解,则剪枝。
  • 剩余体积的侧面积下界 :剩余体积为 N - v,若将其全部做成一个半径为 r[dep+1] 的圆柱(最大可能半径),其侧面积为 2*(N-v)/r[dep+1]。这个值是剩余部分侧面积的下界(半径越大,侧面积越小)。若 s + 下界 ≥ ans,则剪枝。
5. 枚举半径和高度
  • 半径 i
    下限 = dep(保证上面还能放下 dep-1 层)
    上限 = min( sqrt(N-v), r[dep+1]-1 )(不能超过下一层半径,且体积限制)
  • 高度 j
    下限 = dep
    上限 = min( (N-v)/(i*i), h[dep+1]-1 )(体积限制 + 递减约束)
6. 递归与回溯
  • 更新体积:v + i*i*j
  • 更新表面积:s + 2*i*j,若为最底层(dep == M)还需加上 i*i(下底面积)
  • 递归 dfs(dep-1, new_v, new_s)
7. 输出

若找到解则输出最优的 ans,否则输出 0


代码实现

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

const int INF = 0x3f3f3f3f;
int N, M, ans = INF;
int mins[25], minv[25];  // 前 i 层的最小侧面积和最小体积
int r[25], h[25]; // 每层的半径和高度(从下往上存储)

// dep:当前要处理的层数(从 M 向下到 1)
// v:已经使用的体积
// s:已经累积的表面积(最底层下底面积已在 dep==M 时加入)
void dfs(int dep, int v, int s) {
    if (dep == 0) {  // 所有层处理完毕
        if (v == N) ans = min(ans, s);  // 体积恰好为 N 时更新答案
        return;
    }

    // 剪枝1:剩余体积加上已有体积仍不足 N,或已超过 N
    if (v + minv[dep] > N) return;
    // 剪枝2:当前表面积加上剩余层的最小侧面积已经不小于最优解
    if (s + mins[dep] >= ans) return;
    // 剪枝3:剩余体积对应的最小可能侧面积下界估算
    // 剩余体积为 N-v,用当前层的下一层半径(即更大半径)作为半径,
    // 得到剩余侧面积的下界(半径越大,侧面积越小)
    if (s + 2 * (N - v) / r[dep + 1] >= ans) return;

    // 枚举当前层的半径,从大到小
    // 半径下限:至少 dep(保证上面还能放 dep-1 层)
    // 半径上限:不能超过下一层半径-1,且不能超过 sqrt(N-v)(最小高度为1)
    for (int i = min((int)sqrt(N - v), r[dep + 1] - 1); i >= dep; i--) {
        // 枚举当前层的高度
        // 高度下限:至少 dep
        // 高度上限:不能超过下一层高度-1,且受体积限制 (N-v)/(i*i)
        for (int j = min((N - v) / i / i, h[dep + 1] - 1); j >= dep; j--) {
            r[dep] = i;
            h[dep] = j;
            int cur_s = s + 2 * i * j;          // 加上当前层的侧面积
            if (dep == M) cur_s += i * i;       // 最底层额外加上下底面积
            dfs(dep - 1, v + i * i * j, cur_s);
        }
    }
}

int main() {
    cin >> N >> M;
    // 预处理前 i 层的最小体积和最小侧面积(假设半径 = 高 = i)
    for (int i = 1; i <= M; i++) {
        minv[i] = minv[i - 1] + i * i * i;   // 体积:i^3
        mins[i] = mins[i - 1] + 2 * i * i;    // 侧面积:2*i^2
    }
    // 边界条件:第 M+1 层(虚拟层)半径和高度设为无穷大,以便底层枚举时不受限制
    r[M + 1] = h[M + 1] = INF;
    dfs(M, 0, 0);  // 从最底层开始搜索
    cout << (ans == INF ? 0 : ans) << endl;
    return 0;
}

功能分析

1. 解决问题

在给定总体积 (N) 和层数 (M) 的情况下,找到使外表面面积最小的蛋糕制作方案,并输出最小的 (S)(整数)。若无解则输出 0。

2. 算法核心
  • 深度优先搜索:枚举每一层的半径和高度,通过递归遍历所有合法组合。
  • 剪枝优化
    • 可行性剪枝(体积不足或超出)
    • 最优性剪枝(当前面积已不优于已知最优解)
    • 剩余体积的面积下界剪枝
  • 预处理:计算最小可能体积和侧面积,用于剪枝判断。
  • 有序枚举:半径和高度从大到小枚举,优先尝试大值,加速找到较优解并增强剪枝效果。
3. 注意点
  • 表面积的计算方式为 所有层侧面积之和 + 最底层下底面积,这与题目文字描述略有差异,但符合样例和多数 AC 代码的实现。
  • 半径和高度的下限取 dep,保证了递减性且每层至少为 1。

更多系列知识,请查看专栏:《信奥赛C++提高组csp-s知识详解及案例实践》:

https://blog.csdn.net/weixin_66461496/category_13113932.html


各种学习资料,助力大家一站式学习和提升!!!

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int main(){
	cout<<"##########  一站式掌握信奥赛知识!  ##########";
	cout<<"#############  冲刺信奥赛拿奖!  #############";
	cout<<"######  课程购买后永久学习,不受限制!   ######";
	return 0;
}

1、csp信奥赛高频考点知识详解及案例实践:

CSP信奥赛C++动态规划:

https://blog.csdn.net/weixin_66461496/category_13096895.html点击跳转

CSP信奥赛C++标准模板库STL:

https://blog.csdn.net/weixin_66461496/category_13108077.html 点击跳转

信奥赛C++提高组csp-s知识详解及案例实践:

https://blog.csdn.net/weixin_66461496/category_13113932.html

2、csp信奥赛冲刺一等奖有效刷题题解:

信奥赛C++普及组csp-j初赛&复赛真题题解(持续更新) https://blog.csdn.net/weixin_66461496/category_12808781.html 点击跳转

信奥赛C++提高组csp-s初赛&复赛真题题解(持续更新)

https://blog.csdn.net/weixin_66461496/category_13125089.html

3、GESP C++考级真题题解:

GESP(C++ 一级+二级+三级)真题题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12858102.html 点击跳转

GESP(C++ 四级+五级+六级)真题题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12869848.html 点击跳转

GESP(C++ 七级+八级)真题题解(持续更新):

https://blog.csdn.net/weixin_66461496/category_13117178.html

4、csp/信奥赛C++,完整信奥赛系列课程(永久学习):

https://edu.csdn.net/lecturer/7901 点击跳转

· 文末祝福 ·

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int main(){
	cout<<"跟着王老师一起学习信奥赛C++";
	cout<<"    成就更好的自己!       ";
	cout<<"  csp信奥赛一等奖属于你!   ";
	return 0;
}
相关推荐
是个西兰花21 分钟前
linux:命名管道与共享内存
linux·运维·服务器·网络·c++
凡人叶枫31 分钟前
Effective C++ 条款08:别让异常逃离析构函数
java·linux·数据库·c++·嵌入式开发
QiLinkOS1 小时前
QiLink开源生态的三维重构:基于时间、空间与社会价值的底层规则创新白皮书
大数据·c++·人工智能·科技·算法·gitee·开源
牛肉在哪里1 小时前
ros2 从零开始28 监听广播C++
开发语言·c++·算法·机器人
玖玥拾1 小时前
C/C++ 数据结构(二)双向链表
c语言·数据结构·c++
枕星而眠1 小时前
Linux守护进程完全指南:从原理到实战
linux·运维·服务器·c++·后端
QiLinkOS2 小时前
极客精神与商业思维的融合实践(2)
c语言·c++·人工智能·算法·开源协议
charlie1145141912 小时前
现代C++特性指南——constexpr 构造函数与字面类型
开发语言·c++
极客BIM工作室2 小时前
OCCT gp_Trsf 三维变换类深度剖析:经典设计与底层陷阱
c++
醉城夜风~2 小时前
类和对象III
开发语言·c++