二分练习题7 网线主管 题解
题目出处:《信息需奥赛一本通》第七章练习7(Northeastern Europe 2001)
题目描述
仙境的居民们决定举办一场程序设计区域赛。裁判委员会完全由自愿组成,他们承诺要组织一次史上最公正的比赛。他们决定将选手的电脑用星形拓扑结构连接在一起,即将它们全部连到一个单一的中心服务器。为了组织这个完全公正的比赛,裁判委员会主席提出要将所有选手的电脑等距离地围绕在服务器周围放置。
为购买网线,裁判委员会联系了当地的一个网络解决方案提供商,要求能够提供一定数量的等长网线。裁判委员会希望网线越长越好,这样选手们之间的距离可以尽可能远一些。
该公司的网线主管承接了这个任务。他知道库存中每条网线的长度(精确到厘米),并且只要告诉他所需的网线长度(精确到厘米),他都能够完成对网线的切割工作。但是,这次,所需的网线长度并不知道,这让网线主管不知所措。
你需要编写一个程序,帮助网线主管确定一个最长的网线长度,并且按此长度对库存中的网线进行切割,能够得到指定数量的网线。
输入格式
第一行包含两个整数N和K,以单个空格隔开。N(1 <= N <= 10000)是库存中的网线数,K(1 <= K <= 10000)是需要的网线数量。
接下来N行,每行一个数,为库存中每条网线的长度(单位:米)。所有网线的长度至少1m,至多100km。输入中的所有长度都精确到厘米,即保留到小数点后两位。
输出格式
网线主管能够从库存的网线中切出指定数量的网线的最长长度(单位:米)。必须精确到厘米,即保留到小数点后两位。
若无法得到长度至少为1cm的指定数量的网线,则必须输出“0.00”(不包含引号)。
样例输入
4 11
8.02
7.43
4.57
5.39
样例输出
2.00
题目分析
这道题目其实和《最大值最小化》具有相同的解法,几乎是一模一样的题。同样也是二分答案,但是这道题是在实数范围内进行二分。
整数的二分
对于整数的二分,我们二分实现的伪代码是:
int L = 左边界, R = 右边界, res = -1;
while (L <= R) {
int mid = (L + R) / 2;
if (条件满足) {
res = mid;
L = mid + 1; // 或者 R = mid - 1;
}
else {
R = mid - 1; // 或者 L = mid + 1;
}
}
而循环结束时,\(res\) 存储的就是我们想要的答案。
实数的二分
对于实数的二分不能进行 \(L = mid + 1;\) 或者 \(R = mid - 1;\) 这样的操作,并且也不能使用 \(L \le R\) 来作为循环地判断条件。
而是应该使用 \(R - L\) 小于某一个临界值,比如如果我们希望答案精确到小数点后 \(3\) 为,那么我可以在满足条件 \(R- L \lt 10^{-4}\) 的情况下推出循环,因为退出循环的时候 \(L\) 和 \(R\) 在保留 \(3\) 位小数的情况下是相同的结果。
对于实数的二分,我们二分实现的伪代码是:
double L = 左边界, R = 右边界; // 注意这里是实数的表示,L和R最好开成double
while (R - L >= 1e-4) { // 1e-4是科学计数法的表示,用于表示1乘10的-4次方
double mid = (L + R) / 2.0; // 注意这里的mid也是实数
if (条件满足) {
L = mid; // 或者 R = mid;
}
else {
R = mid; // 或者 L = mid;
}
}
最后,退出循环的时候肯定能确保 \(L\) 和 \(R\) 的差距已经很小了,然后我们输出 \(L\) 和 \(R\) 精确到 \(3\) 位小数点的结果就可以了。
注意:在实数的二分下,使用 \(L = mid\) 或者 \(R= mid\) ,而不是在整数二分中的 \(L = mid+1\) 或者 \(R = mid-1\) 。要注意这个细节。
注意:题目中右边界是100km,也就是100000m,处理时要注意这个细节。
实现代码如下:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100010;
int n, k;
double a[maxn];
bool check(double len) {
int cnt = 0;
for (int i = 0; i < n; i ++) cnt += floor(a[i] / len);
return cnt >= k;
}
int main() {
cin >> n >> k;
for (int i = 0; i < n; i ++) cin >> a[i];
double L = 0.0, R = 100000.0;
while (R - L >= 1e-4) {
double mid = (L + R) / 2.0;
if (check(mid)) L = mid;
else R = mid;
}
printf("%.2lf\n", L);
return 0;
}