【普及/提高−】洛谷P1577 ——切绳子

见: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,足够处理题目中可能出现的最大数据量。
  • nk是两个整数变量,分别表示材料的数量和需要切割出的段数。
  • 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。具体逻辑如下:

  1. 初始化计数器ans为 0。
  2. 遍历每个材料,计算该材料可以切割出多少段长度为x的小段,使用floor(z[i]/x)确保结果为整数。
  3. 累加每段材料可切割的段数到ans中。
  4. 如果在遍历过程中发现ans已经达到或超过k,则立即返回true,表示可以切割出足够的段数。
  5. 如果遍历完所有材料后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;
}

主函数实现了二分查找算法,用于找到最大的可行切割长度,具体步骤如下:

  1. 读取输入数据:首先读取材料数量n和需要切割的段数k,然后读取每个材料的长度并存储在数组z中。
  2. 初始化二分查找的左右边界:左边界l初始化为 0,右边界r初始化为一个足够大的值(1e9),确保包含所有可能的答案。
  3. 执行二分查找:在r-l >= 1e-4的条件下循环,这个条件控制了二分查找的精度,当左右边界的差距小于 1e-4 时停止循环。
  4. 计算中间值mid:使用l + (r - l) / 2计算中间值,避免直接使用(l + r) / 2可能导致的溢出问题。
  5. 判断中间值是否可行:调用ch(mid)函数,如果返回true,说明中间值mid是一个可行解,更新左边界l = mid;否则更新右边界r = mid
  6. 输出结果:循环结束后,l的值即为所求的最大可行切割长度,但需要保留两位小数。使用floor(l*100)/100确保结果精确到小数点后两位,然后使用printf("%.2f", ...)输出结果。

算法思路与二分查找的应用

这个问题的核心思路是利用二分查找来高效地找到最优解。二分查找适用于这种最大化最小值或最小化最大值的问题,其关键在于能够快速判断一个给定的值是否可行。

具体来说,二分查找的应用场景需要满足以下条件:

  1. 解空间具有单调性:在这个问题中,如果长度x是可行的,那么所有小于x的长度也都是可行的;反之,如果长度x不可行,那么所有大于x的长度也都不可行。
  2. 存在明确的判断条件:通过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),主要用于存储材料长度的数组。

测试用例

以下是一些测试用例,可以帮助验证代码的正确性:

  1. 基本测试

    • 输入:n=3, k=4z=[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段,满足条件,且为最大可行长度。
  2. 边界测试

    • 输入:n=1, k=1z=[5.0]
    • 输出:5.00
    • 解释:只有一段材料,长度为 5,切割成 1 段,最大长度为 5。
  3. 精度测试

    • 输入:n=2, k=3z=[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  

### 关于洛谷平台上的“绳子”问题 #### 一、浮点数二分查找概述 对于涉及连续区间内最优解的问题,可以采用浮点数形式的二分查找来逼近最佳答案。这类方法特别适用于那些目标函数具有单调性的场景,在给定范围内逐步缩小可能的结果范围直至满足精度需求[^1]。 #### 二、具体到“绳子”的解决方案 针对该特定问题,“绳子”的核心在于找到能够使得割后的每一段都不超过原N根绳中最短者长度的最大值。通过设定合理的上下限并不断调整中间值来进行验证测试,最终可得到近似度较高的解答。 以下是使用C语言编写的解决此问题的一个实例: ```c #include <stdio.h> #define MAX_N 10000 /* 定义最大绳索数量 */ #define EPSILON 1e-5 /* 设定误差界限 */ int n, m; /* 绳子总数n和所需相同长度段m */ double l[MAX_N]; /* 存储各条绳子的实际长度 */ /* 计算当前假设长度mid下能否获得至少m段 */ int check(double mid){ int count = 0; for(int i=0;i<n;++i) count += (int)(l[i]/mid); return count >= m ? 1 : 0; } void solve(){ double low = 0., high = 0.; for(int i=0;i<n;++i) { scanf("%lf", &l[i]); if(l[i]>high) high=l[i]; } while(high-low>EPSILON){ /* 当高低差小于预设阈值时停止迭代 */ double mid=(low+high)/2.;/* 取中位数作为试探值 */ if(check(mid)) /* 如果能获取足够的片段,则尝试更大的尺寸 */ low=mid; else /* 否则减小尺寸继续探索 */ high=mid; } printf("%.4f\n", low); /* 输出保留四位有效数字的结果 */ } ``` 上述代码实现了对输入数据读取以及基于浮点型变量执行标准二分搜索逻辑的过程。注意这里采用了`EPSILON`参数控制循环终止条件,确保输出结果达到题目所要求的小数点后四位精度[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值