程序基本算法习题解析 分蛋糕:小明有n个蛋糕,分给k个人。若想让每个人得到的蛋糕尽量大,分出去的最小的蛋糕有多大?

题目:

小明有n个蛋糕,分给k个人。规定如下:除了大小为1的蛋糕以外,其余大小的蛋糕均分为两个,如果蛋糕的大小是奇数,则切分成的两个蛋糕大小差一(比如大小为7的蛋糕切分成3和4两个小蛋糕)。若想让每个人得到的蛋糕尽量大,分出去的最小的蛋糕有多大?(蛋糕不必每块都分出去)第一行有两个整数n和k,分别是最初蛋糕个数和人数。接下来一行有n个整数,代表每个蛋糕的大小。

思路:

注意一块蛋糕虽然一次只能均分为两个,但切下来的蛋糕还可以继续切分。书上有解析和代码,但是我觉得并不是很好理解,等会我会附上书上的代码,现在讲一下自己的思路。

首先,要保证切分后蛋糕数量大于人数,如若蛋糕数量不够,需对蛋糕进行切分。切分的规则是从最大尺寸蛋糕开始切。切蛋糕时,要根据蛋糕尺寸的奇偶性进行分类讨论。对一种尺寸的蛋糕切分后,需将更新后的蛋糕数量进行统计,判断其是否大于人数,切分到大于人数后,再进行下面的操作。如果将每块蛋糕都切分为1或2后,蛋糕数量仍小于人数,那么就没有办法让每个人都分到蛋糕,因此输出-1。

再讲一讲为什么不直接把以上的蛋糕分出去。题目要求的是让每个人得到的蛋糕尽量大而不是让每个人都能分到蛋糕,因此将蛋糕尺寸为 10 8 6 2 2 这五块蛋糕分给5个人就不是满足题意的分法,因为此时分出去的蛋糕最小为2。而如果将尺寸为10和8的蛋糕进行切分,变成 6 5 5 4 4 2 2七块蛋糕,选择前五块分发,那么分出去的蛋糕最小为4,比之前的方法更优,也是满足题意的解。

如何才能保证分出去的最小蛋糕是所有分法中最大的呢?可以将暂时准备分出去的最小蛋糕与目前最大蛋糕尺寸的一半进行比较,如果准备分出去的最小蛋糕比目前最大蛋糕尺寸的一半还小,那说明这种切分方法不是最优,因为可以将目前最大蛋糕一分为2,那每个人拿到的蛋糕至少不会小于之前准备分出去的最小蛋糕。 

第一步,先对蛋糕尺寸进行排序(为了求最大值,不排序,直接求最大值也可以),找到输入的最大的蛋糕尺寸。然后定义一个存放各尺寸蛋糕的数量的数组cakeNum。cakeNum的角标为蛋糕尺寸,值为该尺寸的蛋糕个数,例如cakeNum[7] = 2就表示蛋糕尺寸为7的蛋糕有2个。对某个尺寸的蛋糕进行切分后,都得将该蛋糕切分前对应的数量清零,切分成的两个尺寸的蛋糕对应的数量加‘1’(不是真正的加1,而是加上切分前该尺寸蛋糕的数量,因为每次不是只切一个,而是将同一尺寸的所有蛋糕都切分)。求当前蛋糕最大尺寸的方法是,遍历整个cakeNum数组(从大到小进行遍历),找到第一个不为0的角标,即为当前蛋糕最大尺寸。

代码如下:

// Chapter11_10.cpp : Defines the entry point for the application.
// 分蛋糕
// 小明有n个蛋糕,分给k个人。规定如下:除了大小为1的蛋糕以外,其余大小的蛋糕均分为两个,
// 如果蛋糕的大小是奇数,则切分成的两个蛋糕大小差一(比如大小为7的蛋糕切分成3和4两个小蛋糕)。
// 若想让每个人得到的蛋糕尽量大,分出去的最小的蛋糕有多大?(蛋糕不必每块都分出去)
// 第一行有两个整数n和k,分别是最初蛋糕个数和人数。
// 接下来一行有n个整数,代表每个蛋糕的大小。
#include "stdafx.h"
#include<iostream>
using namespace std;

//排序函数(从小到大)
void funSort(int *arr,int n)
{
	for(int i=0;i<n;i++)
		for(int j=i+1;j<n;j++)
		{
			if(arr[i] > arr[j])
			{
				int temp = arr[i];
				arr[i] = arr[j];
				arr[j] = temp;
			}
		}
}
int main()
{
	int n,k; //n:最初蛋糕个数,k:人数
	int i,j,count;
	int flag = 0; //标志变量,flag为0表示蛋糕数量小于人数,需要继续分,flag为1表示蛋糕数量大于人数
	int isSolved = 0; //标志变量,isSolved为0表示没有得到最优结果,为1表示得到最优结果
	int ans = 1; //解(分出去的蛋糕最小值)
	cout << "输入最初蛋糕个数n和人数k:";
	cin >> n >> k;
	//动态定义一个数组,存放蛋糕尺寸
	int *cake = new int[n+1];
	cake[0] = 0;
	cout << "分别输入每个蛋糕的大小:" << endl;
	for(i=1;i<=n;i++)
		cin >> cake[i];
	//对蛋糕尺寸进行从小到大排序
	funSort(cake,n+1);
	//求蛋糕的最大尺寸
	int maxSize = cake[n];
	//动态定义一个数组,存放各尺寸蛋糕的数量(角标=蛋糕尺寸)
	int *cakeNum = new int[maxSize+1];
	//初始化cakeNum数组
	for(i=0;i<=maxSize;i++)
		cakeNum[i] = 0;
	//求各尺寸蛋糕对应的数量
	for(i=1;i<=n;i++)
		cakeNum[cake[i]]++;
	while(!isSolved)
	{
		count = 0;
		//判断是否有满足分给k个人的方案(将蛋糕进行最小单元切分(1~2))
		while(flag == 0)
		{
			int sum = 0;
			//计算蛋糕数量
			for(i=maxSize;i>0;i--)
				sum = sum + cakeNum[i];
			//如果切分后的蛋糕大于等于人数
			if(sum >= k)
				flag = 1;
			//对蛋糕进行切分
			else
			{
				int curMaxSize = 0; //当前蛋糕的最大尺寸
				//查找目前最大蛋糕的尺寸(角标从大到小,查找第一个不为0的角标)
				for(j=maxSize;j>0;j--)
				{
					if(cakeNum[j] != 0)
					{
						curMaxSize = j;
						break;
					}
				}
				//如果将每个蛋糕切分为1或2后的总数量小于人数,则无解
				if((curMaxSize == 2 || curMaxSize == 1) && (cakeNum[1]+cakeNum[2])< k)
				{
					flag = 1;  //为了退出内层while循环
					ans = -1;  //没有合适方案,输出-1
					isSolved = 1; //为了退出外层while循环
				}
				//切分蛋糕
				if(curMaxSize >= 2)
				{
					//目前的蛋糕最大尺寸为偶数
					if(curMaxSize%2 == 0)
					{
						//注意cakeNum[curMaxSize]不一定为1
						cakeNum[curMaxSize/2] = cakeNum[curMaxSize/2] + cakeNum[curMaxSize]*2;
						cakeNum[curMaxSize] = 0;  
					}
					//目前的蛋糕最大尺寸为奇数
					else
					{
						//注意cakeNum[curMaxSize]不一定为1
						cakeNum[curMaxSize/2] = cakeNum[curMaxSize/2] + cakeNum[curMaxSize];
						cakeNum[curMaxSize/2+1] = cakeNum[curMaxSize/2+1] + cakeNum[curMaxSize];
						cakeNum[curMaxSize] = 0;
					}
				}
			}
		}
		if(ans != -1)
		{
			//检验是否是满足条件的切分法,如果不满足条件,则继续进行切分
			for(i=maxSize;i>0;i--)
			{
				//当前准备分出去的蛋糕数
				count = count + cakeNum[i];
				//如果蛋糕数量大于等于人数
				if(count >= k)
				{
					flag = 1;
					int curMaxSize = 0; //当前蛋糕的最大尺寸
					//查找目前最大蛋糕的尺寸(角标从大到小,查找第一个不为0的角标)
					for(j=maxSize;j>0;j--)
					{
						if(cakeNum[j] != 0)
						{
							curMaxSize = j;
							break;
						}
					}
					//如果蛋糕最大尺寸的一半小于等于分出去的最小蛋糕,则得到解
					if(curMaxSize/2 <= i)
					{
						isSolved = 1;
						ans = i;
						break;
					}
					//对蛋糕进行切分
					else
					{
						//目前的蛋糕最大尺寸为偶数
						if(curMaxSize%2 == 0)
						{
							//注意cakeNum[curMaxSize]不一定为1
							cakeNum[curMaxSize/2] = cakeNum[curMaxSize/2] + cakeNum[curMaxSize]*2;
							cakeNum[curMaxSize] = 0;  
						}
						//目前的蛋糕最大尺寸为奇数
						else
						{
							//注意cakeNum[curMaxSize]不一定为1
							cakeNum[curMaxSize/2] = cakeNum[curMaxSize/2] + cakeNum[curMaxSize];
							cakeNum[curMaxSize/2+1] = cakeNum[curMaxSize/2+1] + cakeNum[curMaxSize];
							cakeNum[curMaxSize] = 0;
						}
					}
				}
			}
		}
	}
	cout << "分出去的最小蛋糕大小为:" << ans << endl;
	//释放数组空间
	delete [n+1]cake;
	delete [maxSize+1]cakeNum;
	system("pause");
	return 0;
}

运行结果如下:

最后附上书上代码:

#include "stdafx.h"
#include<iostream>
#include<math.h>
using namespace std;

//书上程序
const int N=(1e6+10);
const int M=(1e7+10);
int num[N];
int n,k;
int maxx;
long check[M];
long sum = 0;
int main(int argc,const char *argv[])
{
	bool flag = false;
	int i;
	cin >> n >> k;
	//求得最大的蛋糕大小并对所有蛋糕大小对应单元加1
	for(i=0;i<n;i++)
	{
		cin >> num[i];
		check[num[i]]++;
		maxx = max(num[i],maxx);
	}
	for(i=maxx;i>0;i--)
	{
		sum += check[i]; //当前大小蛋糕的个数
		sum -= check[i*2]; //减去未切分时的蛋糕个数
		//切分有奇偶两种情况,都需要考虑
		if(i*2-1 <= maxx && i!=1)
		{
			sum -= check[i*2-1];
		}
		//蛋糕数等于人数,输出当前蛋糕大小
		if(sum >=k)
		{
			cout << i << endl;
			flag = true;
			break;
		}
		//将当前蛋糕切分
		check[i/2] += check[i];
		check[(i+1)/2] += check[i];
	}
	if(!flag)
		cout << -1 << endl;
	system("pause");
	return 0;
}

运行结果和之前的程序一致。

可以看到,书上的程序明显简洁很多,花费的时间和空间都比我自己写的程序少,但是重在思考,不管用什么方法做出来的,只要认真思考过,就有收获,多学多练,终究会写出优美的程序。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值