C語言DFS(3)___生日蛋糕(深度剪枝)
阿新 • • 發佈:2019-01-05
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的列舉都應該遵從由大到小,因為如果從小到大的話,搜尋更深更廣容易超時.而如果是從大到小列舉的話,那麼每次枚舉出來的體積最大,剩餘體積小,則可搜尋下去的空間就小.即可省時.