生日蛋糕的多种剪枝

题目背景

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)。

输入输出样例

输入样例#1:
100
2
输出样例#1:
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

(2)  Hi  > Hi+1
(3)  Vi+1 = Vi - Ri+1* Ri+1* Hi+1
(4) Si+1 = Si + 2 * Ri+1* Hi+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!



  • 8
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值