/* Program: min-packing.c (Report comments/bugs to chikh@yuntech.edu.tw) Function: 將n個糖磚包裝成一大包,每個糖磚長寬高都是10公分,計算最少面積的包裝紙可打包好。 運作理念:先對n決定可能的分割方式,每一種分割即決定一種包裝方式。 例:n = 4 = 1+3 = 2+2 = 1+1+2 = 1+1+1+1,代表可把糖磚包裝為 a) 僅1排,4個擺成一排 b) 第1排擺1個、第2排擺3個 c) 第1排擺2個、第2排擺2個 d) 第1排擺1個、第2排擺1個、第3排擺2個 e) 第1排擺1個、第2排擺1個、第3排擺1個、第4排擺1個 注意上述a)與e)實為等義情況。 因糖磚總數固定,覆蓋上下面的包裝紙面積相同,不同的包裝方式造成周圍長度有差異。 若能計算每一種分割對應的擺置方式之週長,則可知哪種分割可得最佳組態,問題即得解。 Notes: 1) 透過整數分割,另自訂shape資料結構以解決問題所需 2) 底下程式碼運作皆以磚個數為計量單數,最後才換算為真正的長寬高(公分) 3) 融入「動態規劃」(dynamic programming)技巧,以dimension[]紀錄目前所知最佳組態 */ #include typedef struct { /* 自訂shape資料結構,描述將被包裝的幾何形狀物 */ int n; /* 所含磚數 */ int base; /* 該形狀的最大底邊長度 */ int faces; /* 形狀內糖磚共享接觸面數 */ int circumference; /* 形狀物最短週長 */ } shape; int indx = 0; /* 指示堆疊頂端元素位置 */ int element[100] = {0}; /* 以陣列實作堆疊結構 */ /* 底下dimension[i]紀錄糖磚數為i的條件下,目前所知之最佳組態(週長最短者) */ shape dimension[100] = {{0,0,0,0},{1,1,0,4},{2,2,1,6},{3,3,2,8}}; /* 初始化幾何形狀物的最佳尺寸 */ shape bestDimension(int n, shape s) /* 把dimension[n]與傳入的s形狀併接,回傳合併後的組態 */ { shape result; /* 合併後的結果,將作為函數回傳的內容 */ int minBase; if (s.n == 0) /* s是空的 */ result = dimension[n]; else { /* 把dimension[n]與傳入的s併接,決定新的最佳組態 */ minBase = dimension[n].base <= s.base? dimension[n].base : s.base; /* 計算二個別形狀之底邊最小者,將影響接觸面大小與週長的改變 */ result.n = n+s.n; /* 合併後的新形狀內含的糖磚數 */ result.faces = dimension[n].faces+s.faces + minBase; /* 共享接觸面數將增加 */ result.base = dimension[n].base >= s.base? dimension[n].base : s.base; /* 二個別形狀之底邊最大者為合併後的底邊長度 */ result.circumference = dimension[n].circumference + s.circumference - 2*minBase; /* 換算合併後的新形狀物之週長 */ //if ((dimension[result.n].n == 0) || (dimension[result.n].circumference > result.circumference)) if ((dimension[result.n].n == 0) || (dimension[result.n].circumference > result.circumference) || (dimension[result.n].circumference == result.circumference && dimension[result.n].base < result.base)) /* 若新組態優於dimension[]所記錄內容,則更新 */ dimension[result.n] = result; } //printf("磚數 = %d; 底邊 = %d; 共面數 = %d; 週長 = %d\n",result.n, result.base, result.faces, result.circumference); return result; } shape partition(int n, int max, int addend) /* n:待分割的數、max:最大元素、addend:所採用的分解元素 */ { int i; shape result = {0,0,0,0}; /* 空的幾何形狀 */ static shape bestOne = {0,0,0,1000000}; /* 即將回傳的最佳組態,先預設週長為某超大的數值,隨後將更新 */ element[indx++] = addend; /* 相當於push()動作 */ if (n == 0) { /* 已觸底、無法再分割,把推疊內的內容取出 */ for (i = indx-1; i > 0; i--) /* 逐一合併所有可能分割所對應的幾何形狀物,結果紀錄於result */ result = bestDimension(element[i],result); //printf("磚數 = %d; 底邊 = %d; 共面數 = %d; 週長 = %d\n",result.n, result.base, result.faces, result.circumference); if (bestOne.circumference > result.circumference) bestOne = result; } else /* n > 0,意味著尚可繼續分割 */ for (i = 1; i <= max && i <= n; i++) partition(n-i,i,i); indx--; /* 等同pop()動作 */ return bestOne; /* 回傳最佳形狀 */ } int main() { int n, i, numCube; shape bestLayout; printf("\n*** 最小包裝面積之郵寄包裹程式 ***\n\n輸入糖磚數 ==> "); scanf("%d",&n); //printf("\n最少包裝紙面積 = %d\n",partition(n,n,0).circumference*10*10+2*n*10*10); bestLayout = partition(n,n,0); printf("\n最少包裝紙面積 = %d\n",bestLayout.circumference*10*10+2*n*10*10); printf("\n包裝方式為擺\放若干排:\n"); for (numCube = n, i = 1; numCube > 0; numCube -= bestLayout.base, i++) { bestLayout = dimension[numCube]; printf("第%d排,擺\%d個磚\n",i,bestLayout.base); /* 輸出留意有"許功蓋"問題,因此多加\以避免困擾 */ } return 0; }