1. 程式人生 > >『生日蛋糕 搜索剪枝』

『生日蛋糕 搜索剪枝』

可能 假設 數組 邊界 算法 ins sca 輸出 freopen

<更新提示>

<第一次更新>


<正文>

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 Format

每組數據兩行,第一行為N(N <= 10000),表示待制作的蛋糕的體積為N×πN×π;第二行為M(M <= 20),表示蛋糕的層數為M。

Output Format

僅一行,是一個正整數S(若無解則S=0)。

Sample Input

100
2

Sample Output

68

解析

預備知識:圓柱體體積公式\(v=\pi R^2H\),圓柱體側面積公式\(s'=2\pi RH\),圓柱體底面積公式\(s=\pi R^2\)

輸出和輸入均不考慮\(\pi\),所以直接無視所有公式中的\(\pi\)即可。

直接考慮一個搜索算法,狀態為\((dep,s,v)\),代表當前搜索到第\(dep\)層(從上往下數),表面積為\(s\),體積為\(v\),並且記錄了第\(m\)至第\(dep-1\)層的高度和半徑為\(h\)\(r\)

數組(小寫)。然後對於每一層,枚舉其高度\(H\)和半徑\(R\),進行搜索。

然後考慮如下的剪枝:

  • 上下界剪枝:枚舉\(R \in [dep,min(\lfloor \sqrt{n-v} \rfloor,r_{dep+1}-1)],H\in [dep,min(\lfloor \frac{n-v}{R^2} \rfloor,h_{dep+1}-1)]\)

由於蛋糕\(dep\)層以上還有\(m-dep\)層,高度和半徑大小的要求都是遞減的,所以枚舉的左邊界很容易得到。

對於半徑,其枚舉右邊界顯然不能大於等於\(r_{dep+1}\),不然不滿足遞減性,假設第\(dep\)層就是最後一層,那麽由圓柱體體積公式\(\pi R^2H=\pi (n-v)\)

可以得到半徑\(R\)顯然需要滿足\(R \leq \sqrt{n-v}\),這就是半徑的枚舉範圍。

對於高度,同樣右邊界不能大於等於\(h_{dep+1}\),不然不滿足遞減性,由於已知\(R\),那麽我們也可以用體積公式得到高度\(H\)顯然需要滿足\(H \leq \frac{n-v}{R^2}\),這就是高度的枚舉範圍。

  • 搜索順序剪枝:對於以上推導出的枚舉範圍,從大到小枚舉。

  • 可行性及最優性剪枝:預處理從上向下\(i\)層的最小側面積及體積,即第\(i\)層半徑高度都取\(i\),最小體積\(v_i=i^3\),最小表面積\(s_i=2*i^2\)。如果體積\(v\)加上剩下\(dep+1\)\(1\)的最小體積和大於\(n\)時,可以剪枝。如果表面積\(s\)加上剩下\(dep+1\)\(1\)的最小表面積和大於已經搜到的答案時,可以剪枝。

  • 最優性剪枝:當\(\frac{2(n-v)}{r_{dep}}+s\)大於等於已經搜到的答案時,可以剪枝。

由圓柱體表面積與體積公式可以得到第\(1\)到第\(dep-1\)層的體積之和為
\[ \sum_{i=1}^{dep-1}(h_i*r_i^2) =n-v \]
側面積之和為
\[ 2\sum_{i=1}^{dep-1}(h_i*r_i) \]
那麽可以進行如下推導:
\[ 2\sum_{i=1}^{dep-1}(h_i*r_i) \\=\frac{2}{r_{dep}}\sum_{i=1}^{dep-1}(h_i*r_i)*r_{dep} \\ = \frac{2}{r_{dep}}\sum_{i=1}^{dep-1}(h_i*r_i*r_{dep}) \\ \geq \frac{2}{r_{dep}}\sum_{i=1}^{dep-1}(h_i*r_i^2) \\ =\frac{2(n-v)}{r_{dep}}=x \]
即第\(1\)到第\(dep-1\)層的表面積之和大於等於\(x\),所以如果\(s+x\)大於等於已經搜到的答案時,也可以剪枝。

很多時候,\(dfs\)的剪枝是可以通過如上的縮放方式推導出來的,這就要求我們需要發現題目隱藏的性質,合理利用已知條件進行優化。

\(Code:\)

#include<bits/stdc++.h>
using namespace std;
#define mset(name,val) memset(name,val,sizeof name)
#define filein(str) freopen(str".in","r",stdin)
#define fileout(str) freopen(str".out","w",stdout)
const int N=10000+20,M=20+20,INF=0x3f3f3f3f;
int n,m,h[M],r[M],Minsumv[M],Minsums[M],ans=INF;
inline void input(void)
{
    scanf("%d%d",&n,&m);
}
inline void init(void)
{
    for(int i=1;i<=m;i++)
    {
        Minsumv[i]=Minsumv[i-1]+(i*i*i);
        Minsums[i]=Minsums[i-1]+(2*i*i);
    }
    h[m+1]=r[m+1]=INF;
}
inline void dfs(int dep,int s,int v)
{
    if(!dep)
    {
        if(v==n)ans=min(ans,s);
        return;
    }
    if(2*(n-v)/(r[dep+1])+s>ans)return;
    if(v+Minsumv[dep]>n)return;
    if(s+Minsums[dep]>ans)return;
    for(int R=min((int)sqrt((n-v)*1.0),r[dep+1]-1);R>=dep;R--)
    {
        for(int H=min((n-v)/(R*R),h[dep+1]-1);H>=dep;H--)
        {
            r[dep]=R;h[dep]=H;
            if(dep==m)
                dfs(dep-1,R*R+2*R*H,R*R*H);
            else dfs(dep-1,s+2*R*H,v+R*R*H);
        }
    }
}
int main(void)
{
    input();
    init();
    dfs(m,0,0);
    printf("%d\n",ans==INF?0:ans);
    return 0;
} 


<後記>

『生日蛋糕 搜索剪枝』