传送门:poj1190
由于高度和半径都是整数因此可以用bfs来做,关键是如何剪枝。由于蛋糕是一层层摞上去的,而最底层下底面面积又不算,因此所有底面积的和就相当于一个底面积,即最下面那一层的上底面的面积,剩余部分就都是侧面积了。
经过以上分析,假设最上面一层为第一层,由于下面的蛋糕要比上面的大,因此每一层的最小面积(不考虑底面积)就成了h和r都取i(i为第几层)的时候,即第i层的最小面积为2*i*i,最小体积为i*i*i。求这两项的目的是剪枝,从下往上搜索,如果某一层前面已用体积加上剩余层的最小体积都比给定的值大的时候就可以直接返回,如果某一层前面已有面积加上剩余层的最小面积都比已经求出来的ans大也要直接返回。
还有一个最关键的剪枝:假设dfs到了第i层已经用的奶油体积为v,则剩下的蛋糕体积为n-v,这时候把剩下的所有奶油做成一层所得到的面积肯定是比做成好几层小的,做成一层的面积为2*(n-v)/r,用该面积加上前几层已有面积如果大于已求得的ans,则要直接返回。具体证明详见http://blog.csdn.net/gubojun123/article/details/7804516
#include <iostream>
#include <cstdio>
#include <cstring>
#include<math.h>
#include<algorithm>
#define inf 9999999
using namespace std;
int minv[22],mins[22];
int n,m,ans=inf;
void dfs(int v,int s,int d,int r,int h)
{
if(d==0)
{
if(v==n&&s<ans)
ans=min(ans,s);
return ;
}
if((v+minv[d-1]>n)||(2*(n-v)/r+s>=ans)/*||(s+mins[d-1]>=ans)*/)//三个剪枝,注释起来的剪枝意义不大,有没有无所谓
return ;
for(int i=r-1;i>=d;i--)//该层蛋糕的取值范围为上一层的蛋糕半径-1到层数d
{
if(d==m)//在第m层的时候把总的底面积加上去,以后对于底面积就不用处理了
s=i*i;
int maxh=min((n-v-minv[d-1])/(i*i),h-1);//该层高度的取值上限为该层可用的奶油体积除以该层半径的平方和上一层的高度-1中的较小值
for(int j=maxh;j>=d;j--)
dfs(v+i*i*j,s+2*i*j,d-1,i,j);
}
}
int main()
{
minv[0]=0/*,mins[0]=0*/;
for(int i=1;i<22;i++)//计算每一层对应的最小面积和体积
{
minv[i]=minv[i-1]+i*i*i;
//mins[i]=mins[i-1]+2*i*i;
}
while(~scanf("%d%d",&n,&m))
{
ans=inf;
dfs(0,0,m,n+1,n+1);//这里dfs的初始r和h赋值一定要比理论最大值大,否则会产生错误
if(ans==inf) //r的理论最大值为n/m再开根号,h的理论最大值为n/(m*m)+1.
ans=0; //(体积n为定值,n=r*r*h,r和h的理论最小值都为m,由此可以求得r和h的理论最大值)
printf("%d\n",ans);
}
return 0;
}