快轉到主要內容

Ropes

目錄

題目
#

有 \(n\) \((1 \leq n \leq 10000)\) 條繩子,第 \(i\) 條長度為 \(a_i\) \((1 \leq a_i \leq 10^7)\),你需要從這些繩子他們剪成 \(k\) \((1 \leq k \leq 10000)\) 條等長的線段,請問剪出來的線段最長可以多長?

解題思路
#

這題沒有辦法直接從輸入算出答案。雖然答案是浮點數,沒辦法直接枚舉,但是我們可以「二分搜答案」。

但是,在確認能二分搜答案之前,我們要先確定答案是否有「單調性」。對於每一個 \(x\),如果用長度 \(x\) 沒有辦法切成 \(k\) 段以上,長度比 \(x\) 大就更不可能切 \(k\) 段。如果可以用長度 \(x\) 把繩子切成 \(k\) 段,那比 \(x\) 小的長度也一定可以。

更精確來說,對於每一個 \(m\),我們可以把每一條繩子的 (長度除以 \(m\)) 加起來,在確認總和 (代表能夠分割成幾段) 是否大於等於 \(k\),用此資訊縮小二分搜的範圍。這個演算法的時間複雜度是 \(O(Nlog(max(a_i)))\) ,可以通過這題。

常見錯誤
#

  • 記得加 setprecision,不然會 WA on test 9
  • 浮點數二分搜不要加 \((l = m + 1)\) 或 \((r = m - 1)\),不然可能會剛好跳過答案。

完整程式碼
#

#include <bits/stdc++.h>

using namespace std;
 
#define int long long
 
signed main() {
    int n, k;
    cin >> n >> k;
    vector<int> a(n);
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    double l = 0, r = 1e7;
    for (int j = 0; j < 100; j++) {
        double m = (l + r) / 2;
        int cnt = 0;
        for (int i = 0; i < n; i++) {
            cnt += floor((double)(a[i]) / m);
        }
        if (cnt >= k) {
            l = m;
        }
        else {
            r = m;
        }
    }
    cout << setprecision(7) << l << "\n";
}
Piau 的筆記本
作者
Piau 的筆記本
希望我寫下來的東西能夠長久的記在我的腦中