见:P1577 切绳子 - 洛谷
题目描述
有 N 条绳子,它们的长度分别为 Li。如果从它们中切割出 K 条长度相同的绳子,这 K 条绳子每条最长能有多长?答案保留到小数点后 2 位(直接舍掉 2 位后的小数)。
输入格式
第一行两个整数 N 和 K,接下来 N 行,描述了每条绳子的长度 Li 。
输出格式
切割后每条绳子的最大长度。答案与标准答案误差不超过 0.01 或者相对误差不超过 1% 即可通过。
输入输出样例
in:
4 11
8.02
7.43
4.57
5.39
out:
2.00
说明/提示
对于 100% 的数据 0<Li≤100000.00,0<n≤10000,0<k≤10000
第一步
code
#include<bits/stdc++.h>
using namespace std;
const int q=1e4+5;
int n,k;
double z[q];bool ch(double x){int ans=0;for(int i=0;i<n;i++){ans+=floor(z[i]/x);if(ans>=k) return true;}return false;
}int main(){cin>>n>>k;for(int i=0;i<n;i++) cin>>z[i];double l=0,r=1e9;while(r-l>=1e-4){double mid=l+(r-l)/2;if(ch(mid)) l=mid;else r=mid;}printf("%.2f",floor(l*100)/100);return 0;
}
第二步
分析
问题背景与应用场景
这个问题在现实生活中有很多应用场景,比如:
- 电缆切割:将不同长度的电缆切割成若干等长的小段,以满足至少
k
段的需求,同时最大化每段的长度。 - 土地划分:将不同面积的土地划分为至少
k
块相等面积的小地块,求最大可能的地块面积。 - 布料裁剪:将不同长度的布料裁剪成至少
k
条等长的布条,以最大化每条布条的长度。
代码详细分析
全局变量与常量定义
const int q=1e4+5;
int n,k;
double z[q];
q
是一个常量,表示数组的最大长度,这里设置为 10005,足够处理题目中可能出现的最大数据量。n
和k
是两个整数变量,分别表示材料的数量和需要切割出的段数。z[q]
是一个双精度浮点数数组,用于存储每个材料的长度。
核心判断函数ch(double x)
bool ch(double x){int ans=0;for(int i=0;i<n;i++){ans+=floor(z[i]/x);if(ans>=k) return true;}return false;
}
这个函数的作用是判断是否可以将所有材料切割成至少k
段,每段长度为x
。具体逻辑如下:
- 初始化计数器
ans
为 0。 - 遍历每个材料,计算该材料可以切割出多少段长度为
x
的小段,使用floor(z[i]/x)
确保结果为整数。 - 累加每段材料可切割的段数到
ans
中。 - 如果在遍历过程中发现
ans
已经达到或超过k
,则立即返回true
,表示可以切割出足够的段数。 - 如果遍历完所有材料后
ans
仍小于k
,则返回false
。
主函数main()
int main(){cin>>n>>k;for(int i=0;i<n;i++) cin>>z[i];double l=0,r=1e9;while(r-l>=1e-4){double mid=l+(r-l)/2;if(ch(mid)) l=mid;else r=mid;}printf("%.2f",floor(l*100)/100);return 0;
}
主函数实现了二分查找算法,用于找到最大的可行切割长度,具体步骤如下:
- 读取输入数据:首先读取材料数量
n
和需要切割的段数k
,然后读取每个材料的长度并存储在数组z
中。 - 初始化二分查找的左右边界:左边界
l
初始化为 0,右边界r
初始化为一个足够大的值(1e9),确保包含所有可能的答案。 - 执行二分查找:在
r-l >= 1e-4
的条件下循环,这个条件控制了二分查找的精度,当左右边界的差距小于 1e-4 时停止循环。 - 计算中间值
mid
:使用l + (r - l) / 2
计算中间值,避免直接使用(l + r) / 2
可能导致的溢出问题。 - 判断中间值是否可行:调用
ch(mid)
函数,如果返回true
,说明中间值mid
是一个可行解,更新左边界l = mid
;否则更新右边界r = mid
。 - 输出结果:循环结束后,
l
的值即为所求的最大可行切割长度,但需要保留两位小数。使用floor(l*100)/100
确保结果精确到小数点后两位,然后使用printf("%.2f", ...)
输出结果。
算法思路与二分查找的应用
这个问题的核心思路是利用二分查找来高效地找到最优解。二分查找适用于这种最大化最小值或最小化最大值的问题,其关键在于能够快速判断一个给定的值是否可行。
具体来说,二分查找的应用场景需要满足以下条件:
- 解空间具有单调性:在这个问题中,如果长度
x
是可行的,那么所有小于x
的长度也都是可行的;反之,如果长度x
不可行,那么所有大于x
的长度也都不可行。 - 存在明确的判断条件:通过
ch
函数可以快速判断一个给定的长度是否可行。
二分查找的时间复杂度是 O (log (max_length)),其中 max_length 是右边界的值。在每次迭代中,需要遍历所有材料一次,时间复杂度为 O (n),因此总的时间复杂度为 O (n log (max_length)),这使得算法在处理大规模数据时仍然高效。
精度控制与输出处理
在处理浮点数二分查找时,精度控制是一个关键问题。代码中使用r - l >= 1e-4
作为循环条件,确保结果的精度在小数点后四位。这是因为在实际应用中,我们通常只需要保留两位小数的结果,而额外的精度可以避免由于浮点数计算误差导致的结果偏差。
输出处理部分使用了floor(l*100)/100
来确保结果精确到小数点后两位。这是因为直接使用%.2f
格式化输出可能会进行四舍五入,而题目要求是直接截断到两位小数。例如,如果计算结果是 3.149,floor(3.149*100)/100
会得到 3.14,而不是 3.15。
代码优化与扩展
输入验证
当前代码没有对输入进行验证,在实际应用中可以添加输入验证以增强代码的健壮性。例如:
if (k <= 0) {cout << "Error: k must be a positive integer." << endl;return 1;
}
右边界优化
初始右边界设置为 1e9 可能过大,可以根据输入数据动态设置右边界,例如:
double max_z = 0;
for(int i=0;i<n;i++) {cin>>z[i];max_z = max(max_z, z[i]);
}
double l=0, r=max_z;
这样可以减少二分查找的迭代次数,提高效率。
处理无解情况
如果所有材料的总长度小于k
,则无论如何都无法切割出k
段,此时应输出 0。可以在开始时添加检查:
double sum = 0;
for(int i=0;i<n;i++) {sum += z[i];
}
if (sum < k) {cout << "0.00" << endl;return 0;
}
代码可读性优化
可以添加注释来提高代码的可读性,例如:
// 检查是否可以将所有材料切割成至少k段,每段长度为x
bool ch(double x){// ... 函数体 ...
}// 主函数:二分查找最大可行切割长度
int main(){// ... 主函数体 ...
}
复杂度分析
- 时间复杂度:O (n log (max_length)),其中 n 是材料的数量,max_length 是右边界的值。
- 空间复杂度:O (n),主要用于存储材料长度的数组。
测试用例
以下是一些测试用例,可以帮助验证代码的正确性:
-
基本测试:
- 输入:
n=3, k=4
,z=[10, 20, 30]
- 输出:
15.00
- 解释:每段长度为 15 时,可切割出
0+1+2=3
段,不足 4 段;每段长度为 14 时,可切割出0+1+2=3
段,不足 4 段;每段长度为 13 时,可切割出0+1+2=3
段,不足 4 段;每段长度为 12 时,可切割出0+1+2=3
段,不足 4 段;每段长度为 11 时,可切割出0+1+2=3
段,不足 4 段;每段长度为 10 时,可切割出1+2+3=6
段,满足条件,且为最大可行长度。
- 输入:
-
边界测试:
- 输入:
n=1, k=1
,z=[5.0]
- 输出:
5.00
- 解释:只有一段材料,长度为 5,切割成 1 段,最大长度为 5。
- 输入:
-
精度测试:
- 输入:
n=2, k=3
,z=[1.0, 2.0]
- 输出:
0.66
- 解释:每段长度为 0.66 时,可切割出
1+3=4
段,满足条件;每段长度为 0.67 时,可切割出1+2=3
段,满足条件,但 0.67 不是最大可行长度,最大可行长度为 0.666...,截断后为 0.66。
- 输入:
总结
这段代码通过二分查找高效地解决了材料切割的最大化最小值问题,关键在于合理设计判断函数ch
和精确控制二分查找的边界与精度。代码结构清晰,算法效率高,适用于处理大规模数据。在实际应用中,可以根据具体需求进行进一步优化和扩展,如添加输入验证、处理无解情况等,以提高代码的健壮性和实用性。
完结撒花hi✿(。◕ᴗ◕。)✿
对了
听说给点赞+关注+收藏的人会发大财哦(o゚▽゚)o