[NOI1999]生日蛋糕
题目描述
7月17日是Mr.W的生日,ACM-THU为此要制作一个体积为Nπ的M层生日蛋糕,每层都是一个圆柱体。设从下往上数第i(1 <= i <= M)层蛋糕是半径为Ri, 高度为Hi的圆柱。当i < M时,要求Ri > Ri+1且Hi > Hi+1。由于要在蛋糕上抹奶油,为尽可能节约经费,我们希望蛋糕外表面(最下一层的下底面除外)的面积Q最小。令Q = Sπ
请编程对给出的N和M,找出蛋糕的制作方案(适当的Ri和Hi的值),使S最小。(除Q外,以上所有数据皆为正整数)
输入格式
有两行,第一行为N(N <= 20000),表示待制作的蛋糕的体积为Nπ;第二行为M(M <= 15),表示蛋糕的层数为M。
输出格式
仅一行,是一个正整数S(若无解则S = 0)
很容易发现每次选择一对半径和高之后都会往下延伸出去很多种选择,并且数据是这样的小......所以不难想到搜索可以胜任这题。
表面积的公式太复杂,反正我是懒得写。如果小学没挂科 可以想起来不管堆出的蛋糕长什么样,其俯视图都是一个圆,并且半径为底层的半径。所以我们可以把顶层的面积省掉,只算侧面积即可。
然后找状态。每层的状态无非就是:当前的层数dep,当前的体积nowv,当前的侧面积nows,上一次选择的高度preh,上一次选择的半径prer。由于习惯,我用两个数组h[]和r[]记录每次选择的高度和半径。然后搜就可以了。接下来是喜闻乐见的TLE
剪枝酱刚才上厕所了......我们把能用的剪枝都往上套一遍:
1.上下界剪枝。先列出圆柱的体积公式:
\[V={\pi}R^2H
\]
不难发现半径最大的情况就是:剩下的体积只放一个高度为1的圆柱,那么此时:
\[\pi(N-NowV)={\pi}R^2H\\
{\Rightarrow}R=\sqrt{\frac{N-NowV}{H}}=\sqrt{N-NowV}
\]
所以我们可以对R上界进行剪枝,枚举R的范围就变成了:
\[R{\in}[dep,min(r[dep+1]-1,\sqrt{N-NowV})]
\]
类似地,剩下体积只放一个圆柱时高度H最大,此时:
\[{\pi}(N-NowV)={\pi}R^2H\\
{\Rightarrow}H=\frac{N-NowV}{R^2}
\]
所以H的范围就是:
\[H{\in}[dep,min(h[dep+1]-1,\frac{N-NowV}{R^2})]
\]
2.搜索顺序优化。显然从大往小搜索更优,所以我们从上界往下界枚举即可。
3.可行性剪枝。可以预处理出当前层数为dep时剩下部分的最小体积min_v。如果当前体积nowv加上min_v大于n,那么无论如何也得不到结果,直接回溯。
4.1.最优化剪枝。首先预处理出当前层数为dep时剩下部分的最小侧面积min_s,如果当前侧面积nows加上min_s和底面积已经大于得出的较优解ans时直接回溯。
4.2.最优化剪枝2——喜闻乐见的放缩法。
设当前的层数为dep,可以表示出1~dep-1层的总体积:
\[{\pi}V=\pi(N-NowV)={\pi}\sum_{i=1}^{dep-1}r[i]^2*h[i]
\]
以及当前的总侧面积:
\[{\pi}S=2{\pi}\sum_{i=1}^{dep-1}r[i]*h[i]
\]
\[2{\pi}\sum_{i=1}^{dep-1}r[i]*h[i]=\frac{2\pi}{r[dep]}\sum_{i=1}^{dep}r[i]*h[i]*r[dep]{>}\frac{2}{r[dep]}{\pi}\sum_{i=1}^{dep}r[i]^2*h[i]=\frac{2}{r[dep]}*{\pi}(N-NowV)
\]
所以当发现:
\[NowS+\frac{2}{r[dep]}*(N-NowV)+r[m]^2{\geq}ans
\]
时,直接回溯。
5.记忆化。没有什么好记的,跳过。
剪完枝了~
还有最后一个问题:一开始的r[m]和h[m]上界式子中的r[dep+1]-1和h[dep+1]-1一项初始化为多少呢?为了不搜漏,我们应该考虑表面积最大的情况。
\[{\because}{\pi}N={\pi}R^2H\\
{\therefore}H=\frac{N}{R^2}\\
{\because}{\pi}S=2{\pi}RH+{\pi}R^2\\
{\therefore}S=\frac{2N}{R}+R^2\\
不难发现f(R)=S是个增函数。\\
{\because}当H=H_{min}=1时,R=R_{max}=\sqrt{N}\\
{\therefore}当R=\sqrt{N}时S有最大值,为N+2\sqrt{N}
\]
类似地,让H成为自变量时得出:
\[S=2\sqrt{NH}+\frac{N}{H}
\]
通过几何画板分析得出当H≥1时函数f(H)=S也是单调递增的。
所以只需要初始化r[m+1]=sqrt(n)和h[m+1]=n即可。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define maxn 21
using namespace std;
int h[maxn],r[maxn];
int min_v[maxn],min_s[maxn];
int n,m,ans=0x3f3f3f3f;
inline int read(){
register int x(0),f(1); register char c(getchar());
while(c<'0'||'9'<c){ if(c=='-') f=-1; c=getchar(); }
while('0'<=c&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x*f;
}
void dfs(int dep,int nows,int nowv){
if(dep==0){ if(nowv==n&&ans>nows+r[m]*r[m]) ans=nows+r[m]*r[m]; return; }
if(nowv>n) return;
if(nowv+min_v[dep]>n) return;
if(nows+min_s[dep]+r[m]*r[m]>=ans||nows+2*(n-nowv)/r[dep+1]+r[m]*r[m]>=ans) return;
for(register int i=min(r[dep+1]-1,(int)floor(sqrt(n-nowv)));i>=dep;i--){
for(register int j=min(h[dep+1]-1,(int)floor((n-nowv)/(i*i)));j>=dep;j--){
r[dep]=i,h[dep]=j;
dfs(dep-1,nows+2*i*j,nowv+i*i*j);
}
}
}
int main(){
n=read(),m=read();
r[m+1]=sqrt(n),h[m+1]=n;
for(register int i=1;i<=m;i++){
min_v[i]=min_v[i-1]+i*i*i;
min_s[i]=min_s[i-1]+2*i*i;
}
dfs(m,0,0);
printf("%d\n",ans==0x3f3f3f3f?0:ans);
}