题目:7月17日是Mr.W的生日,ACM-THU为此要制作一个体积为NπNπ的M层生日蛋糕,每层都是一个圆柱体。
设从下往上数第i层蛋糕是半径为RiRi, 高度为HiHi的圆柱。
当i < M时,要求RiRi > RiRi+1且HiHi > HiHi+1。
由于要在蛋糕上抹奶油,为尽可能节约经费,我们希望蛋糕外表面(最下一层的下底面除外)的面积Q最小。
令Q = Sπ ,请编程对给出的N和M,找出蛋糕的制作方案(适当的RiRi和HiHi的值),使S最小。
除Q外,以上所有数据皆为正整数 。
输入格式
输入包含两行,第一行为整数N(N <= 10000),表示待制作的蛋糕的体积为NπNπ。
第二行为整数M(M <= 20),表示蛋糕的层数为M。
输出格式
输出仅一行,是一个正整数S(若无解则S = 0)。
数据范围
1≤N≤100001≤N≤10000,
1≤M≤201≤M≤20
输入样例:
100
2
输出样例:
68
题解思路:
该题是经典的爆搜剪枝问题,要求体积不变下,表面积最小(除了第一层的底面积) ,那么从每一层蛋糕的角度来看,蛋糕的半径和高度都是单调递减,从枚举每一层的半径和高度,枚举的时候有一个原则,即优先枚举 选择决策少的来扩展,半径和高度这两个变量搜索范围的确定有点意思,后面会讲解。
关于爆搜剪枝问题,剪枝当然是重中之重,该题可以提出四个剪枝方法;剪枝经常应用于深搜和广搜当中,策略就是寻找过滤条件,提前减少不必要的搜索路径。
搜索解的讨论(搜索极其重视搜索顺序,个头大的先搜索,方案数将较少):1、贪心的思想是保持每步最优解,以达到全局最优解2、爆搜如何产生最优解呢?将需要搜索顺序排成从大到小,从中进行搜索。
从蛋糕从下往上搜索,上表面积就是最下面圆的面积,求解过程注重侧面积和体积即可,
假设当前状态有 s,v,rdep+1∼m,hdep+1∼ms,v,rdep+1∼m,hdep+1∼m, dep对应的状态,有:
n−v≥r2dephdep
n−v≥rdep2hdep
有上界:
rdep=min(n−v,rdep+1−1)
rdep=min(n−v,rdep+1−1)
hdep=min(n−vr2dep,hdep+1−1)
hdep=min(n−vrdep2,hdep+1−1)
有下界:
rdep=dep,hdep=dep
rdep=dep,hdep=dep
很明显,ri=i,hi=iri=i,hi=i 是最小的情况,
它们对应的 minsi=2rihi=2i2,minvi=r2ihi=i3minsi=2rihi=2i2,minvi=ri2hi=i3
如果当前结果加最小情况超过答案,那么后面就不会有解。
在有解的情况下,有下面不等式成立:
s1∼dep−1=2∑i=1dep−1rihi,v1∼dep−1=n−v=∑i=1dep−1r2ihi
s1∼dep−1=2∑i=1dep−1rihi,v1∼dep−1=n−v=∑i=1dep−1ri2hi
ri
s1∼dep−1=2∑i=1dep−1rihi=2rdep∑i=1dep−1rihirdep≥2rdep∑i=1dep−1ri2hi
s1∼dep−1≥2(n−v)rdep
s1∼dep−1≥2(n−v)rdep
即,2(n−v)rdep2(n−v)rdep 是后面未求面积的下限,如果 2(n−v)rdep+s2(n−v)rdep+s 大于答案,那么这样的状态是不会更优的。
作者:VMice
链接:https://www.acwing.com/solution/AcWing/content/1500/
#include
#include
#include
using namespace std;
const int N=25,Inf=1e9;
//全局变量
int n,m;
int R[N],H[N];//每层的半径和高度
int minv[N],mins[N];
int res=Inf ; //定义初始答案为正无穷
/*
爆搜:对每层蛋糕的半径和高度进行搜索,注意更新半径和高度
*/
void dfs(int u,int v,int s)//args:当前层数、当前体积、当前表面积
{
//四个剪枝方法
if(v+minv[u]>n)return;
if(s+mins[u]>=res)return;
//最重要的优化
if(s+2*(n-v)/R[u+1]>=res)return;
if(!u) //如果到达最上一层,输出最上一层,非常重要的一个语句,目的是使爆搜达到条件后终止
{
if(n==v)res=s;//在最后一层搜索时,主要的操作步骤是找到满足条件的解后结束
return;
}
//终于理解了为什么第u层的r,h最小半径为u,因为最顶层r,h最小为1,那么依次递增,最大层的m的r,h最小值为m
for(int r=min((int)(double)sqrt(n-v),R[u+1]-1);r>=u;r--)//枚举状态,r,h从大到小枚举,同时有范围
for(int h=min((n-v)/r/r,H[u+1]-1);h>=u;h--) //
{
//注意当最后一层确定时,要加第一层的低盘面积
int t=0;
if(u==m)t=r*r;
R[u]=r,H[u]=h; //新知识点:更新当前的半径和高度
dfs(u-1,v+r*r*h,s+2*r*h+t);//参数更新及传递
}
}
int main()
{
//读入数据
cin>>n>>m;
//初始化minv,mins
for(int i=1;i<=m;i++)
{
// minv[i+1]=minv[i]+i*i*i;
//mins[i+1]=minv[i]+2*i*i;
minv[i]=minv[i-1]+i*i*i;
mins[i]=mins[i-1]+2*i*i;
}
//处理搜索边界#这里设置哨兵,帮助完成边界
R[m+1]=H[m+1]=Inf;//?
dfs(m,0,0);//赋初值最底下一层,初始面积为0,初始表面积为0
cout<
cout<
cout<
return 0;
}