C语言DFS(3)___生日蛋糕(深度剪枝)

Description

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外,以上所有数据皆为正整数) 

Input

有两行,第一行为N(N <= 10000),表示待制作的蛋糕的体积为Nπ;第二行为M(M <= 20),表示蛋糕的层数为M。

Output

仅一行,是一个正整数S(若无解则S = 0)。

Sample Input

100
2

Sample Output

68

样例:

#include <iostream>
#include <stdio.h>
#include <cmath>
using namespace std;
int n,m,minv[21],mins[21];  
//V=n*pi  m 层数 自顶向下1.2.3...m  
//minv[i]表示i层到0层加起来的最小总体积 minvs 最小表面积
const int inf=1000000000;   // inf 足够大就可以 int(32)  -2^31~2^31-1=2147483647  
int best;				//best 最小表面积

void DFS(int depth,int sumv,int sums,int r,int h) //深度优先搜索 自底m向上搜索 r h表示当前层得半径和高度
//sumv已经用的总体积 sums已经生成的总表面积
{
	if(0==depth)
	{
		if(sumv==n&&sums<best)           //搜索完成 更新最小表面积best
			best=sums;
		return;
	}
	// 三个剪枝条件:
	//1、已经搜索过的体积加上还未搜索过的最小体积不能比总体积n 大
    //2、已经搜索过的表面积加上还未搜索过的最小表面积不能比之前的最小总表面积best 大 
    //3、n-sumv既所剩体积记作dv 还需要的表面积为s
	//s=2*ri*hi+2*r(i-1)*h(i-1)+... >=2*ri*hi*ri/r+2*r(i-1)*h(i-1)*r(i-1)/r+...
	//	                            =2*dv/r(i从depth-1取,r为当前半径 ri/r<1)
    // 所以得到还需要的最小表面积s=2*(n-sumv)/r,如果最小的s和已经搜索过的表面积sums依然比best大 就不用继续搜索了
	if(sumv+minv[depth-1]>n||sums+mins[depth-1]>best||sums+2*(n-sumv)/r>=best)  //剪枝如上所述
		return;
	for( int i=r-1;i>=depth;i--)    //递减顺序枚举depth层半径的每一个可能值,这里第depth层的半径最小值为depth
	{
		if(depth==m)
			sums=i*i;      //俯视蛋糕底面积作为外表面积的初始值(总的上表面积,以后只需计算侧面积)
		int maxh=((n-sumv-minv[depth-1])/(i*i)<h-1)? (n-sumv-minv[depth-1])/(i*i):h-1;
		//maxh最大高度,即depth层蛋糕高度的上限,(n-sumv-minv[dep-1])表示第depth层最大的体积
		for(int j=maxh;j>=depth;j--)    //同理,第depth层的最小高度值为depth
			DFS(depth-1,sumv+i*i*j,sums+2*i*j,i,j);  //递归搜索子状态
	}
}
int main(void)
{
	
	while(scanf("%d%d",&n,&m)!=EOF)
	{
		best=inf;
		int rmax=(int)sqrt((double)n); //rmax初始半径 底层半径 最大值为sqrt(n)
		int hmax=n;                    //hmax初始高度 高度最大为 n
		minv[0]=mins[0]=0;
		for(int i=1;i<=m;i++)
		{                              //初始化minv和mins数组
			minv[i]=minv[i-1]+i*i*i;   //从顶层(即第1层)到第i层的最小体积minv[i]成立时第j层的半径和高度都为j
			mins[i]=mins[i-1]+2*i*i;
		}
		DFS(m,0,0,rmax,hmax);
		if(best==inf)
			best=0;        //无解
		if(best==0)
			cout<<0<<endl;
		else
			cout<<best<<endl;
	}
	return 0;
}

样例二:

#include <iostream>
#include <cmath>
#include <cstdio>
using namespace std;
const int inf=0xfffffff ;
int preminv[21] ;
int ri[21],hi[21] ;
int total,floornum ;
int ans ;
int dfs (int n,int v,int s,int maxr,int maxh)
{
      int res ;
      int i,tmpr,tmph,tmpsum ;
      if (n>floornum){
            if (v==total && s+ri[1]*ri[1]<ans){
                  ans = s+ri[1]*ri[1] ;
                  return 1 ;
            }
      }
      for (ri[n]=maxr-1 ; ri[n]>=floornum-n+1 ; ri[n]--){
            res = 2.0*(total-v)/ri[n] + ri[1]*ri[1] ;
            if (res + s >= ans) continue ;
            for (hi[n]=maxh-1 ; hi[n]>=floornum-n+1 ; hi[n]--){
                  if (v+ri[n]*ri[n]*hi[n]+preminv[floornum-n]>total)  continue ;
                  tmpr = ri[n] ,tmph = hi[n],tmpsum = 0 ;
                  for (i=n ; i<=floornum ; i++){
                        tmpsum += tmpr*tmpr*tmph ;
                        tmpr-- , tmph-- ;
                  }
                  if (v+tmpsum<total) continue ;
                  dfs(n+1,v+ri[n]*ri[n]*hi[n],s+2*ri[n]*hi[n],ri[n],hi[n]) ;
            }
      }
      return 1 ;
}
int main()
{
      int tmpv,tmpr,tmph ;
      int i ;
      tmpv=0 ;
      for (i=1 ; i<21 ; i++){
            tmpv += i*i*i ;
            preminv[i] = tmpv ;
      }
      while (scanf("%d%d",&total,&floornum)!=EOF){
            ans = inf ;
            tmpv = total - preminv[floornum-1] ;
            if (tmpv>0){
                  tmpr = sqrt (1.0*tmpv/floornum) + 1 ;
                  tmph = 1.0*tmpv/(floornum*floornum) + 1 ;
                  dfs(1,0,0,tmpr,tmph) ;
            }
            if (ans==inf){
                  ans = 0 ;
            }
            printf("%d\n",ans) ;
      }
    return 0;
}


注意:此题搜索很容易办到,但是应利用合理剪枝来节省时间,而不管从层数,还是对于每层中r,h的枚举都应该遵从由大到小,因为如果从小到大的话,搜索更深更广容易超时.而如果是从大到小枚举的话,那么每次枚举出来的体积最大,剩余体积小,则可搜索下去的空间就小.即可省时.


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值