题目来自于博主算法大师的专栏:最新华为OD机试C卷+AB卷+OJ(C++JavaJSPy) https://blog.csdn.net/banxia_frontend/category_12225173.html
题目描述
孙悟空爱吃蟠桃,有一天趁着蟠桃园守卫不在来偷吃。已知蟠桃园有 N 棵桃树,每颗树上都有桃子,守卫将在 H 小时后回来。
孙悟空可以决定他吃蟠桃的速度K(个/小时),每个小时选一颗桃树,并从树上吃掉 K 个,如果树上的桃子少于 K 个,则全部吃掉,并且这一小时剩余的时间里不再吃桃。
孙悟空喜欢慢慢吃,但又想在守卫回来前吃完桃子。
请返回孙悟空可以在 H 小时内吃掉所有桃子的最小速度 K(K为整数)。如果以任何速度都吃不完所有桃子,则返回0。
输入描述
第一行输入为 N 个数字,N 表示桃树的数量,这 N 个数字表示每颗桃树上蟠桃的数量。
第二行输入为一个数字,表示守卫离开的时间 H。
其中数字通过空格分割,N、H为正整数,每颗树上都有蟠桃,且 0 < N < 10000,0 < H < 10000。
输出描述
吃掉所有蟠桃的最小速度 K,无解或输入异常时输出 0。
用例1
输入
2 3 4 5
4
输出
5
用例2
输入
2 3 4 5
3
输出
0
题意分解
题目的意思是说:
1、孙悟空要吃蟠桃,面前摆了 n 堆,一堆一堆地吃;
2、孙悟空 1 小时能吃 k 个;但如果一堆少于 k 个,那也得花一小时 (一小时到了,才吃下一堆)
3、如果 1 堆大于 k 个,那么超过 k 的部分也算 1 小时。(例如,一堆 10 个,k=3 时,孙悟空花 3 小时吃了 9 个,剩下的 1 个也算 1 小时,也就是总用时 4 小时)
4、问:只给 h 小时,孙悟空要吃多慢(k 多小),才能充分占用这 h 小时
解题思路
- 首先,我们需要找到孙悟空吃蟠桃的速度的上限。因为孙悟空每小时只会吃一堆蟠桃,所以他能够吃完的最大速度就是一小时内能吃掉最多蟠桃的那一堆的数量。因此,我们可以遍历所有蟠桃堆,求出其中的最大值,作为孙悟空吃蟠桃的速度的上限。
- 接下来,我们可以使用二分查找来查找孙悟空吃蟠桃的速度。具体来说,我们可以在速度的范围 [1, max_piles] 中进行二分查找,每次取中间值 mid 作为孙悟空的吃蟠桃速度,然后计算以该速度吃蟠桃需要的时间。如果时间小于等于给定的时间 h,说明孙悟空可以在给定时间内吃完所有蟠桃,我们可以尝试更小的速度;否则说明孙悟空不能在给定时间内吃完所有蟠桃,我们需要尝试更大的速度。最终,当速度的范围缩小到只剩下一个数时,该数就是孙悟空吃蟠桃的最小速度。
- 计算以给定速度吃蟠桃需要的时间的方法如下。遍历所有蟠桃堆,计算每一堆蟠桃需要的时间,然后求和即可。对于一堆蟠桃,如果它的数量小于等于孙悟空的吃蟠桃速度,那么孙悟空可以在一小时内吃完这一堆蟠桃,需要的时间就是 1 小时;否则,孙悟空需要吃 ceil(piles[i] / k) 个小时才能吃完这一堆蟠桃。
代码
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX(a, b) a > b ? a : b
// 判断以速度k是否能在h小时内吃完所有桃子
int can_finish(int *peach, int count, int h, int k) {
int ans = 0;
for (int i = 0; i < count; i++) {
ans += (int)ceil((double)peach[i] / k);
}
return ans <= h;
}
int main() {
char input[10000];
fgets(input, 10000, stdin);
// 将输入分割并存入数组
int peach[10000];
int count = 0;
char *token = strtok(input, " ");
while (token != NULL) {
peach[count++] = atoi(token);
token = strtok(NULL, " ");
}
int h;
scanf("%d", &h);
if (h < count) {
printf("0\n");
return 0;
}
int left = 1, right = 0;
for (int i = 0; i < count; i++) {
right = MAX(peach[i], right);
}
// 二分查找最小吃桃速度
// 因为孙悟空每小时只会吃一堆蟠桃,所以他能够吃完的最大速度就是一小时内能吃掉最多蟠桃的那一堆的数量。
while (left < right) {
int mid = (left + right) / 2;
if (can_finish(peach, count, h, mid)) {
right = mid;
} else {
left = mid + 1;
}
}
printf("%d", left);
return 0;
}
注意:
1、ceil()
- 在调用
ceil()
函数之前,必须包含<math.h>
头文件。 ceil()
函数返回的是double
类型,即使结果是一个整数也是如此。如果需要将结果转换为整数类型(如int
),可以使用类型转换操作符(如(int)result
)。但是,请确保这样做不会导致数据丢失,尤其是在结果超出目标整数类型的表示范围时。
2、ans += (int)ceil((double)peach[i] / k);
该语句执行的是一个浮点数除法后向上取整并累加的操作。具体步骤分解如下:
-
(double)peach[i]
:首先将数组peach
中的第i
个元素(假设是整型)转换为double
类型,这样可以进行浮点数除法而不丢失精度。 -
(double)peach[i] / k
:对转换后的浮点数与变量k
进行除法运算,得到一个双精度浮点数结果。 -
ceil((double)peach[i] / k)
:使用ceil()
函数对上述除法结果进行向上取整操作。如果商不是整数,ceil()
会返回大于或等于该商的最小整数。 -
(int)ceil(...)
:将ceil()
函数返回的双精度浮点数转换回整数类型,因为通常情况下在需要整数值参与进一步计算时,会将浮点数转换成整数。 -
ans += ...
:最后,将这个转换后的整数值累加到变量ans
上,即执行一次加法自增操作,每次循环都将新的整数值添加到ans
中。
整体来看,这条语句的作用是在处理等分问题时,将数组peach
中每个元素值按照给定的整数k
进行均分(向上取整),并将这些“份”累加到变量ans
中,用于统计总共分成了多少“份”。
3、本题中strtok的使用
设输入的input是:2 3 4 5
对于下面的语句:
char *token = strtok(input, " ");
while (token != NULL) {
peach[count++] = atoi(token);
token = strtok(NULL, " ");
}
这段代码是用来处理输入字符串(本例中为"2 3 4 5")并将其转换为整数数组的过程。详细解释如下:
-
char *token = strtok(input, " ");
- 首先调用
strtok()
函数,传入的参数是输入字符串input
和分隔符(这里是空格" ")。这个函数会将输入字符串按照空格分割,并返回第一个子串(即第一个数字“2”),并将剩余部分在内部进行标记。
- 首先调用
-
while (token != NULL) { ... }
- 开始一个循环,只要
strtok()
函数返回的子串不为空(即还有更多的数字可以读取),就继续执行循环体内的语句。
- 开始一个循环,只要
-
在循环体内:
-
peach[count++] = atoi(token);
- 使用
atoi()
函数将当前token
指向的字符串转换成整数,并将该整数值存入名为peach
的整数数组中。count++
表示每次存储时都将数组索引递增1,因此后续的数字会依次存放在数组的不同位置上。 - 对于本例中的第一次循环,它会将字符串"2"转换为整数2并存入
peach[0]
。
- 使用
-
token = strtok(NULL, " ");
- 再次调用
strtok()
函数,由于本次传入的第一个参数是NULL
,所以函数会在上一次分割的基础上继续查找下一个子串,也就是下一个被空格分隔的数字。 - 当找到新的子串时,如第二次循环找到的是"3",那么接下来会将"3"转换为整数存入数组的下一个位置
peach[1]
。
- 再次调用
-
循环将继续进行,直到所有由空格分隔的数字都被提取并转换为整数存入peach
数组中。对于给定的输入"2 3 4 5",最终peach
数组的内容将会是:peach[0] = 2
, peach[1] = 3
, peach[2] = 4
, peach[3] = 5
。