题目背景
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)。
输入输出样例
100 2
68
我们来推推公式吧
转变思路,用搜索怎么样?
数据库
用( i , Ri , Hi , Vi , Si )表示第i层蛋糕的一个状态。其中Ri ,Hi分别为第i层蛋糕的半径和高,Vi , Si分别表示做完第i层蛋糕后剩下的蛋糕体积和当前蛋糕的表面积。可见,
初始状态:(1,R1,H1,n-R1*R1*H1,R1*R1+2*R1*H1)
目标状态:(m,Rm,Hm,0,Sm)
于是,我们的目标是找到一条从初始状态到任意目标状态的路径,并且Sm最小.
扩展的规则
( i , Ri , Hi , Vi , Si ) -> ( i+1,Ri+1,Hi+1,Vi+1,Si+1)
满足:
(1) Ri > Ri+1
基本算法
确定第一层蛋糕的大小
根据上一层蛋糕的大小确定下一层蛋糕该怎么做
看是否符合条件
1)是否做到了m层
2)是否最终体积为0
3)是否当前面积最小
若上述条件成立,则保留当前最优值,否则继续做下一层蛋糕,若重做蛋糕
优化??
(1)因为知道余下的蛋糕体积,因此可以估算一下余下侧面积,
这样我们可以就加入如下剪枝条件:
if 当前的表面积 + 余下的側面积 > 当前最优值 then exit
设已经做了i层蛋糕,则还需做m-i层,
Si’:为第i层蛋糕的侧面积,
FSi:余下的侧面积,怎么求FSi ?
因为:
2Vi= 2Ri+1 * Ri+1 * Hi+1 + ...+ 2Rm * Rm * Hm= Ri+1 * Si+1 + ...+ Rm * Sm
≤ Ri+1 * (Si+1+ ...+ Sm)= Ri+1 * FSi
所以:
FSi ≥ 2Vi / Ri+1
因此剪枝条件为:
if Si-1 + 2 * Vi-1 / Ri >当前最优值 then exit
(2)如果剩下的蛋糕材料太少,不能保证做到m层,那么没有必要继续往下做了,设,
最m层半径和高都为1,Rm=Hm=1
第m-1层半径和高都为2,Rm-1=Hm-1=2
…………
第 i +1层半径和高都为i, Ri = Hi = m – i
这样, 余下的m-i层的最小体积为
MINi=∑(k=1 to m-i)k^3
因此,剪枝条件为,
if ( Vi< MINi) return;
(3)如果剩下的蛋糕材料太多,以最大的方式做完m层, 仍有材料剩余,那么没有必要继续往下做了,设,
第i+1层半径和高分别为,Ri+1 = Ri – 1 , Hi+1 = Hi –1
第i+2层半径和高分别为,Ri+2 = Ri – 2 , Hi+2 = Hi –2
…………
第 m层半径和高分别为,Ri+m = Ri –m ,Hi+m= Hi –m
这样, 余下的m-i层的最大体积为
MAXi,R,H=∑(j=i to M)[(Rj-j)^2*(Hj-j)]
因此,剪枝条件为,
if ( Vi > MAXi,R,H) return;
小节
最优化剪枝
剪枝1: if (Si-1 + 2 * Vi-1 / Ri) >当前最优值 return;
可行性剪枝
剪枝2:if ( Vi< MINi ) return;
剪枝3:if ( Vi > MAXi,R,H ) return;
剪枝原则
正确、高效
深度搜索消耗时间 ≈ 每个节点操作系数 × 节点个数
优化
1)减少节点个数——这就是剪枝优化;
2)减少每个节点的操作系数——即程序操作量。
这里写代码
这是第一次做的,,,一般水的数据还能过大概2w以上的数据就不行了
#include<bits/stdc++.h>
using namespace std;//V=r*r*h(pai) S=r*r+2*r*h (pai)
int ans,volume,Surfacearea=1.2*1e+9,_min[30];
int _max(int layer,int r,int h)
{
int max1=0;
while(layer--)
{
r--,h--;
max1+=r*r*h;
}
return max1;
}
void dfs(int r,int h,int v,int s,int layer)
{
if(2*(volume-v)/r+s>=Surfacearea) return;
if(v+_max(layer,r,h)<volume) return;
if(v+_min[layer]>volume) return;
if(!layer) {Surfacearea=min(Surfacearea,s);return;}
for(int r1=layer;r1<r;r1++)
for(int h1=layer;h1<h;h1++)
dfs(r1,h1,v+r1*r1*h1,s+2*r1*h1,layer-1);
}
int main()
{
int r,h,layer;
scanf("%d%d",&volume,&layer);
for(int i=1;i<=layer;i++)
_min[i]=i*i*i+_min[i-1];
for(r=layer;r<=sqrt(volume/layer);r++)
for(h=layer;h<=volume/(r*r);h++)
dfs(r,h,r*r*h,r*r+2*r*h,layer-1);
if(Surfacearea==1.2*1e+9) printf("0");
else printf("%d",Surfacearea);
return 0;
}
这是第二次做的 10w都没有问题
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;//V=r*r*h(pai) S=r*r+2*r*h (pai)
int n,m,ans,minv[10],mins[10];
int read()
{
int x=0,y=1;char c;
while(c=getchar(),(c<'0'||c>'9')&&c!='-');
if(c=='-')y=-1;else x=c-'0';
while(c=getchar(),c>='0'&&c<='9')x=x*10+c-'0';
return x*y;
}
void dfs(int rr,int hh,int v,int s,int k)
{
if(!k){if(s<ans&&v==n)ans=s;return;}
if(k>=hh||k>=rr)return;//可行性剪枝rr hh会不断减小如果层数比hh rr还大若r h每次减1(下限)当r h到1
if(v+minv[k]>n)return;//可行性剪枝+最小体积
if(v+rr*rr*hh*k<n)return;//可行性剪枝+最大体积
if(s+mins[k]>ans)return;//最优性剪枝+最小面积
if(2*(n-v)/rr+s>=ans)return;//剩余的体积能够成的最小面积+当前面积>答案 剪枝
for(register int r=rr-1;r>=k;r--)//实践证明倒搜更快
for(register int h=hh-1;h>=k;h--)
dfs(r,h,v+r*r*h,s+2*r*h,k-1);
}
int main()
{
n=read();m=read();
ans=1.2*1e+9;
for(register int i=1;i<10;i++)
minv[i]=i*i*i+minv[i-1],
mins[i]=2*i*i+mins[i-1];
for(register int h=m;h<=n/m;h++)
for(register int r=m;r<=sqrt(n/h);r++)
dfs(r,h,r*r*h,r*r+2*r*h,m-1);
if(ans==1.2*1e+9)printf("0\n");
else printf("%d\n",ans);
return 0;
}
这是我第一次发文,有些欠缺的地方请多多包涵。
Thanks!