题目
有一块矩形大蛋糕,长和宽分别是整数w 、h。现要将其切成m块小蛋糕,每个小蛋糕都必须是矩形、且长和宽均为整数。切蛋糕时,每次切一块蛋糕,将其分成两个矩形蛋糕。请计算:最后得到的m块小蛋糕中,最大的那块蛋糕的面积下限。
假设w= 4, h= 4, m= 4,则下面的切法可使得其中最大蛋糕块的面积最小。
假设w= 4, h= 4, m= 3,则下面的切法会使得其中最大蛋糕块的面积最小:
输入
共有多行,每行表示一个测试案例。每行是三个用空格分开的整数w, h, m ,其中1 ≤ w, h, m ≤ 20 , m ≤ wh. 当 w = h = m = 0 时不需要处理,表示输入结束。
输出
每个测试案例的结果占一行,输出一个整数,表示最大蛋糕块的面积下限。
样例输入
4 4 4
4 4 3
0 0 0
样例输出
4
6
解题思路
-
寻找最优子结构
把一个大小为w * h蛋糕切成m块,需要切m-1次,求m块蛋糕中最大块蛋糕的最小值。根据动态规划算法的定义,将该问题分解成在若干子问题解的基础上得到最终的解。即切1次得到2块蛋糕中最大块的最小值,将其存储起来。在这一子问题解的基础上,计算切2次得到的最小值,将其存储起来,计算下一子问题的解。以此类推,直到得到最终的解。 -
归纳状态转移方程
建立一个三维数组ways(w,h,m),w=1、2……W,h=1、2……H,m=0、1、2……M-1,表示大小为w * h的蛋糕,切m次所得到的最大蛋糕的最小值。
SV表示第一刀竖着切的最优解:
SV = min{Si},i = 1、2……W-1,Si = 第一刀竖着切在第i个位置上的最优解
Si = max{ways(i, h, k) , ways(w-i, h, m-k-1)}, k = 0、1……m-1 比较左边切 i 刀,右边切 m-1-i 刀的最优解,取其中较大的一个
SH表示第一刀横着切的最优解:
SH = min{Si},i = 1、2……W-1,Si = 第一刀横着切在第i个位置上的最优解
Si = max{ways(w, i, k) , ways(w, h-i, m-k-1)}, k = 0、1……m-1 比较上边切 i 刀,下边切 m-1-i 刀的最优解,取其中较大的一个
代码实现:
递归法:
# include <iostream>
# include <string.h>
using namespace std;
const int INF = 0xfffffff; //用于表示无穷大
int ways[22][22][22]; //用于存储当前状态
int Divid_the_cake(int w, int h, int m)
{
if(w * h < m + 1){
ways[w][h][m] = INF;
return INF;
}
else if(!m)
{
ways[w][h][m] = w * h;
return w * h;
}
else if(ways[w][h][m]) return ways[w][h][m];
int maxV = INF;
int maxH = INF;
for(int i = 1; i < w; i++){ //第一刀竖着切在第 i 个位置上
for(int j = 0; j < m; j++){ // 比较左边切 i 刀,右边切 m-1-i 刀的最优解,取其中较大的一个
maxV = min(maxV, max(Divid_the_cake(i, h, j), Divid_the_cake(w - i, h, m - j - 1)));
}
}
for(int i = 1; i < h; i++){ //第一刀横着切在 i 个位置上
for(int j = 0; j < m; j++){ //比较上边切 i 刀,下边切 m-1-i 刀的最优解,取其中较大的一个
maxH = min(maxH, max(Divid_the_cake(w, i, j), Divid_the_cake(w, h - i, m - j - 1)));
}
}
ways[w][h][m] = min(maxV,maxH); // 选择竖着切和横着切中最优解
return ways[w][h][m];
}
int main()
{
int w,h,m;
memset(ways,0,sizeof(ways));
Divid_the_cake(22,22,22);
while(cin >> w >> h >> m && w && h && m) {
cout << ways[w][h][m - 1] <<endl;
}
return 0;
}
递推法
# include <iostream>
# include <string.h>
using namespace std;
const int INF = 0x3f3f3f3f; //用于表示无穷大
int ways[22][22][22]; //存储最优解的数据,即切成 m 块蛋糕时,最大块蛋糕的最小面积
int main()
{
int w,h,m;
memset(ways,INF,sizeof(ways));
for(int i = 1; i <= 22; ++i){
for(int j = 1; j <= 22; ++j){ //蛋糕大小从小枚举到大
for(int k = 0; k <= 22; ++k){//切蛋糕刀数从小枚举到大
if(ways[j][i][k] != INF) ways[i][j][k] = ways[j][i][k]; //使用蛋糕的对称性进行优化
else if(i * j < k + 1) ways[i][j][k] = INF;
else if(!k) ways[i][j][k] = i * j;
else{
for(int ii = 1; ii < i; ++ii) //第一刀竖着切在第 ii 个位置上
for(int kk = 0; kk < k; ++kk) //比较左边切 kk 刀,右边切 k-1-kk 刀的最优解,取其中较大的一个
ways[i][j][k] = min(ways[i][j][k], max(ways[ii][j][kk], ways[i - ii][j][k - kk - 1]));
for(int jj = 1; jj < j; ++jj) //第一刀横着切在第 ii 个位置上
for(int kk = 0; kk < k; ++kk) //比较上边切 kk 刀,下边切 k-1-kk 刀的最优解,取其中较大的一个
ways[i][j][k] = min(ways[i][j][k], max(ways[i][jj][kk], ways[i][j - jj][k - kk - 1]));
}
}
}
}
while(cin >> w >> h >> m && w && h && m) cout << ways[w][h][m - 1] <<endl;
return 0;
}
对递推法的优化:
使用对称性对递推法进行优化,宽为w,高为h的蛋糕切m次所得的解,实际上与宽为h,高为w的蛋糕切m次所得的解一样,所以当计算出ways(w,h,m)时,就可知ways(h,w,m)。即在递推法的代码中加入条件
if(ways[j][i][k] != INF) ways[i][j][k] = ways[j][i][k];