DFS 剪枝优化求解生日蛋糕最小表面积问题

80 阅读4分钟

生日蛋糕

题目描述

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

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

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

请编程对给出的 NNMM,找出蛋糕的制作方案(适当的 RiR_iHiH_i 的值),使 S=QπS=\dfrac{Q}{\pi} 最小。

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

输入格式

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

第二行为 MMM15M \leq 15),表示蛋糕的层数为 MM

输出格式

输出一个整数 SS,若无解,输出 00

样例 #1

样例输入 #1

100
2

样例输出 #1

68

题目分析

题意简述

根据蛋糕的体积与层数,求出蛋糕的制作方案使得 S=QπS=\frac{Q}{\pi} 最小。保证 Ri>Ri+1Hi>Hi+1R_i>R_{i+1}且H_i>H_{i+1}

模型抽象

求重叠圆柱体的最小表面积。

初步思考

先思考表面积的计算方式,蛋糕表面积 = 上表面 + 侧面。其中从俯视的角度去看蛋糕,上表面实际为最底下的圆面积。而侧面积每一层都不同,计算公式为 Si=2πrihiS_i=2 \pi {r_i} h_i ,体积为 πri2hi\pi {r_i}^2 h_i

暴力解法

逐层遍历,把每个可能的半径与高度进行尝试。到m层时再进行打擂台。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int ans=2e9;
int r[20],h[20],n,m;
void dfs(int d,int s,int v){
  //d-已处理层数 s-当前s 当前v
  if(d==m){//m层处理完
    if(v==n){//满足体积为n
      ans=min(ans,s);//打擂台找最小s
    }
    return ;
  }	
  //枚举所有的半径与高度的组合
  for(int i=r[d]-1;i>=1;i--){
    for(int j=h[d]-1;j>=1;j--){
      r[d+1]=i;
      h[d+1]=j;
      if(d==0)//第一层时,额外加上上表面
        dfs(d+1,s+i*i+2*i*j,v+i*i*j);
      else
        dfs(d+1,s+2*i*j,v+i*i*j);
      }
  }
}
int main(){
  cin>>n>>m;
  r[0]=sqrt(n);
  h[0]=sqrt(n);
  dfs(0,0,0);
  if(ans==2e9) cout<<0;
  else cout<<ans;
  return 0;
}

优化方向

当前搜索时,每一层的半径、高度组合过多,可以尝试进行剪枝。

优化思考

当前存在两个限制条件:1. 表面积 2. 体积。若搜索过程中体积已超出限定的n则不用继续搜索。整体寻找的最小的表面积,当前若sanss\ge ans ,则越往后只会越来越大,也不需要继续。

void dfs(int d,int s,int v){
  //d-已处理层数 s-当前s 当前v
  if(s>=ans) return ;//最优性剪枝
  if(v>n) return ;//可行性剪枝
  ...
}

当前的剪枝还不够,还需要考虑其他方向。对于体积与侧面积的计算公式为 v=πr2h,s=2πrhv=\pi r^2 h ,s=2 \pi r h。当前剩余体积为 nvn-v,高度设 h=nvr2πh=\frac{n-v}{r^2 \pi},对应的表面积为 s=h×2πr=nvr2π×2πr=2(nv)/rs=h\times2\pi r=\frac{n-v}{r^2\pi}\times2\pi r=2(n-v)/r。若预测出的表面积已经超过了已存储的最小表面积,则不需要继续枚举对应的半径、高度组合了。

void dfs(int d,int s,int v){
  //d-已处理层数 s-当前s 当前v
  if(s>=ans) return ;//最优性剪枝
  if(v>n) return ;//可行性剪枝
  if((n-v)/r[d]*2+s>ans) return;//剩余体积构成的面积,超过了记录的最小值
  ...
}

解题流程

  1. 步骤1:逐层枚举所有可能的半径、高度组合,直到处理到第m层。
  2. 步骤2:加入最优性剪枝、可行性剪枝。
  3. 步骤3:预测剩余体积构成的面积,进行剪枝。

代码实现

#include<bits/stdc++.h>
using namespace std;
int ans=2e9;
int n,m;
int r[20],h[20];
void dfs(int d,int s,int v){
  //d-已经确定好的层数,s-当前的面积 
  if(s>=ans) return;//最优性剪枝 
  if(v>n) return;//可行性剪枝
  if((n-v)/r[d]*2+s>ans) return;//剩余体积构成的面积,超过了记录的最小值 
  if(d==m){
    if(v==n){
      ans=s;
    }
    return;
  }
  //缩小范围 
  for(int i=r[d]-1;i>=m-d;i--){
    for(int j=h[d]-1;j>=m-d;j--){
      r[d+1]=i;
      h[d+1]=j;
      if(d==0) dfs(d+1,s+i*i+2*i*j,v+i*i*j);
      else dfs(d+1,s+2*i*j,v+i*i*j);
    }
  }
}
int main(){
  cin>>n>>m;
  r[0]=sqrt(n);
  h[0]=n;
	
  dfs(0,0,0);
  if(ans==2e9) cout<<0;
  else cout<<ans;
  return 0;
}

总结回顾

本题的关键在于剪枝的处理:

  1. 当前体积超过给定体积,则结束。
  2. 当前表面积\ge已记录的最小表面积。
  3. 估算出剩余体积构成的表面积,利用预估结果进行剪枝。