程序设计与算法二郭炜动态规划算法017分蛋糕及其解题思路

题目

有一块矩形大蛋糕,长和宽分别是整数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

解题思路

  1. 寻找最优子结构
    把一个大小为w * h蛋糕切成m块,需要切m-1次,求m块蛋糕中最大块蛋糕的最小值。根据动态规划算法的定义,将该问题分解成在若干子问题解的基础上得到最终的解。即切1次得到2块蛋糕中最大块的最小值,将其存储起来。在这一子问题解的基础上,计算切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];
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值