一、题意

输入n条线段,线段之间没有包含关系,要求你把每条线段截成原来的一条子线段,使得改变之后的所有线段长度相等且互不相交(端点可以重合)。求每条线段的最大长度,并用分数表示。

二、解析

刚看到要用分数表示时,我是很懵的,但后来仔细一想,其实这只是出题人的虚晃一枪,我们首先求出小数解,然后再转变为分数即可。

如何求得小数解呢?我们很容易发现,这道题直接求出最大解很难,但是判定某个解是否符合题意是容易的,因此这就很容易让我们想到之前讲过的“二分答案”法。之前的二分答案是二分整数值,这道题只要二分double值即可,这样其实简单的多,不需要许多细节处理,唯一要注意的就是double值之间不能直接判定相等,必须只用fabs()结合设定一个常数精度值eps来实现。具体见代码。

三、代码

#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
const int maxn = 100000 + 5;
const double INF = 1e6 + 5, eps = 1e-10;
struct section {
    int l, r;
    bool operator < (const section& rhs) const {
        return l < rhs.l;
    }
} a[maxn];
int n;

bool isOK(double len) {
    double r = -INF;  // 维护一个“上一线段的右端值”
    for(int i = 0; i < n; i ++) {
        r = max(r, (double)a[i].l) + len;
        if(r > a[i].r) return 0;  // 超过线段原右端值则说明该长度失败
    }
    return 1;
}

void output(double d) {  // 小数转分数输出的方法
    int ans_p = INF, ans_q = 1;
    for(int q = 1; q <= n; q ++) {
        int p = (int)(d * q + 0.5);
        if(fabs((double)ans_p / ans_q - d) > fabs((double)p / q - d)) ans_p = p, ans_q = q;
    }
    cout << ans_p << "/" << ans_q << endl;
}

int main() {
    while(cin >> n) {
        for(int i = 0; i < n; i ++) cin >> a[i].l >> a[i].r;
        sort(a, a + n);
        double l = 0, r = INF;
        while(fabs(l - r) > eps) {  // 二分答案,double要用fabs进行精度控制
            double mid = (l + r) / 2;
            if(isOK(mid)) l = mid;
            else r = mid;
        }
        output(l);
    }
}

四、归纳

  • 二分答案也可以在double类型的基础上进行,与整数二分的区别:
    • 无需考虑细节
    • double值(l、r)之间不能直接判定相等,必须只用fabs()结合设定一个常数精度值eps来实现
  • 看到输出分数不要慌张,小数通过枚举分母的方法可以轻松转变为分数,当然是否通过小数转换的方式还得具体题目具体分析
  • 看到直接求解困难,但是判定容易的题目,可以考虑使用二分答案法