NOI 1999 生日蛋糕

洛谷传送门

JDOJ传送门

Description

7月17日是Mr.W的生日,ACM-THU为此要制作一个体积为Nπ的M层生日蛋糕,每层都是一个圆柱体。

NOI 1999 生日蛋糕_表示层设从下往上数第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<=40),表示蛋糕的层数为M。

Output

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

Sample Input

100 2

Sample Output

68

HINT

圆柱公式

体积V=πR2H

侧面积A’=2πRH

底面积A=πR2


题解:

搜索+剪枝。

剪枝讲解走这边:

浅谈常见剪枝方式

众所周知,提升搜索效率有两种方式:第一种是从枚举顺序入手,分析答案从上到下枚举会更快达到答案还是从下到上枚举会更快达到答案。第二种就是剪枝方式,可行性剪枝、最优化剪枝、排除等效冗余和记忆化等等。

这道题有四种剪枝方式:两种可行性剪枝,两种最优化剪枝。

首先,如果剩下的层所剩下的最小的体积都比你目前枚举到的剩下的体积要大,或者剩下的层所剩下的最大的体积都比你目前枚举到的剩下的体积要小,那么肯定是不可行的。这个要好好理解。

然后,如果当前面积加上剩下的层的最小面积还是比当前的ans要大,那么大可不必。

最后,如果当前面积已经比ans要大,那么也大可不必。

10pts代码,没加任何剪枝,只能过样例。

#include<cstdio>
#include<algorithm>
using namespace std;
const int INF=998244353;
int n,m;
int ans=INF;
bool prune(int x,int v,int s)
{
    return 0;
}
void dfs(int r,int h,int step,int s,int v)
//r,h表示上一层,step表示层数,s当前表面积,v当前剩余体积
{
    if(prune(m-step+1,v,s))
        return;
    if(step>m)
    {
        if(!v)
            ans=min(ans,s);
        return;
    }
    for(int i=r-1;i>=m-step+1;i--)
        for(int j=h-1;j>=m-step+1;j--)
            dfs(i,j,step+1,s+2*i*j,v-i*i*j);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int r=m;r*r<=n;r++)
        for(int h=m;h*m*m<=n;h++)
            dfs(r,h,2,r*r+2*h*r,n-r*r*h);
    printf("%d",ans);
    return 0;
}

60pts代码,只加了两个剪枝。

#include<cstdio>
#include<algorithm>
using namespace std;
const int INF=998244353;
int n,m;
int ans=INF;
bool prune(int x,int v,int s)
{
    if(v<x*x*(x+1)*(x+1)/4)
        return 1;
    if(s>ans)
        return 1;
    return 0;
}
void dfs(int r,int h,int step,int s,int v)
//r,h表示上一层,step表示层数,s当前表面积,v当前剩余体积
{
    if(prune(m-step+1,v,s))
        return;
    if(step>m)
    {
        if(!v)
            ans=min(ans,s);
        return;
    }
    for(int i=r-1;i>=m-step+1;i--)
        for(int j=h-1;j>=m-step+1;j--)
            dfs(i,j,step+1,s+2*i*j,v-i*i*j);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int r=m;r*r<=n;r++)
        for(int h=m;h*m*m<=n;h++)
            dfs(r,h,2,r*r+2*h*r,n-r*r*h);
    printf("%d",ans);
    return 0;
}

满分代码:

#include<cstdio>
#include<algorithm>
#define R register
using namespace std;
const int INF=998244353;
int n,m;
int ans=INF;
inline int maxv(int r,int h,int x)
{
    int ret=0;
    for(R int i=x;i>=1;i--)
    {
        r--,h--;
        ret+=r*r*h;
    }
    return ret;
}
inline int mins(int r,int h,int x)
{
    int ret=0;
    for(R int i=1;i<=x;i++)
        ret+=2*i*i;
    return ret;
}
inline bool prune(int r,int h,int x,int v,int s)
{
    if(v<x*x*(x+1)*(x+1)/4)
        return 1;
    if(s>ans)
        return 1;
    if(s+mins(r,h,x)>ans)
        return 1;
    if(v>maxv(r,h,x))
        return 1;
    return 0;
}
inline void dfs(int r,int h,int step,int s,int v)
//r,h表示上一层,step表示层数,s当前表面积,v当前剩余体积
{
    if(step>m)
    {
        if(!v)
            ans=min(ans,s);
        return;
    }
    if(prune(r,h,m-step+1,v,s))
        return;
    for(R int i=r-1;i>=m-step+1;i--)
        for(R int j=h-1;j>=m-step+1;j--)
            dfs(i,j,step+1,s+2*i*j,v-i*i*j);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(R int r=m;r*r<=n;r++)
        for(R int h=n/r/r;h>=m;h--)
            dfs(r,h,2,r*r+2*h*r,n-r*r*h);
    printf("%d",ans);
    return 0;
}