快轉到主要內容

CSES-1085 Array Division

標籤: 二分搜
目錄


題目連結:https://cses.fi/problemset/task/1085

題意
#

題目給定一個包含 \( n \) 個正整數的陣列,要求將其切分為 \( k \) 個連續的子陣列。
目標是讓這些子陣列的總和之最大值越小越好。
我們需要求出能達成的最小「最大和」是多少。

思路
#

「求最大值最小化」是標準的二分搜尋法(Binary Search)應用情境。
與其直接尋找切分位置,不如改變切入點:
若規定所有子陣列的和「不能超過」數值 \( m \),我們最少會將陣列切成幾段?

這將尋找最佳切法的問題轉化為檢驗問題。
在掃描陣列時,我們採取貪婪策略,盡量將數字放入同一個子陣列。
當總和即將超過 \( m \) 時,才進行切分並開啟新的子陣列。

  • 若段數不超過 \( k \),表示上限 \( m \) 充裕,我們可以嘗試縮小 \( m \)。
  • 若段數超過 \( k \),表示上限 \( m \) 過低,需要提高 \( m \)。

二分搜的下界應為陣列中的「最大值」(確保單一元素能作為子陣列),
上界為「整個陣列的總和」(即不切分)。
我們在這兩個端點間進行二分搜尋,直到區間收斂。

程式碼
#

#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define fi first
#define se second
#define INF LONG_LONG_MAX/1000
#define WA() cin.tie(0)->sync_with_stdio(0)
#define all(x) (x).begin(), (x).end()
#define int long long
#define PII pair<int, int>

signed main() { WA();
    int n, k; cin >> n >> k;
    vector<int> v(n);
    int total = 0, ans = 0;
    // 讀入陣列同時計算總和,作為二分搜的上界
    for (auto &i : v) cin >> i, total += i;
    
    // l 為陣列最大值(下界),r 為陣列總和+1(上界)
    for (int l = *max_element(all(v)), r = total+1; l < r;) {
        // m 為這次嘗試的子陣列總和上限
        int m = l + ((r-l) >> 1), cnt = 0, sum = 0;
        
        // 貪心將數字塞入子陣列,超過 m 就切斷結算
        for (auto &i : v) {
            if (sum + i > m) {
                sum = i;
                cnt++;
            }
            else sum += i;
        } 
        // 處理最後剩下的那段
        cnt += sum > 0;
        
        // 若段數不超過 k,表示上限 m 可行,嘗試縮小上限
        if (cnt <= k) ans = r = m;
        // 如果段數太多,代表 m 太嚴苛,必須提高
        else l = m+1;
    }
    
    // 輸出達成條件的最小上限
    cout << ans;
}
Piau 的筆記本
作者
Piau 的筆記本
希望我寫下來的東西能夠長久的記在我的腦中