一、题意

输入包含若干组数据。
每组数据输入一个n, k。然后输入一个长度为n的序列。
要求你把这个序列划分为k段,记每一段的数字和为S(i),要求使得S(i)的最大值尽可能地小。
输出划分结果。
若有多解,则使前面的段尽可能地短。

二、解析

看到“最大值尽可能小”这种字眼,第一下就应该反应出可能要使用二分答案的方法了。
本题就是一个典型的二分答案。

即通过二分法枚举S(i)的最大值x,然后对于每一次枚举,判断是否可以保证分成k段后,每一段的和都不超过x,这是判断是容易的,可以在O(n)时间完成,然后二分的过程可以在O(logM)时间完成,M为和的最大可能值。这样总时间复杂度为O(nlgM)。

ps: 这题的输出有点坑,注意一下就行了,因为不是重点。

三、代码

#include <iostream>
#include <vector>
using namespace std;
using LL = long long;
const int maxn = 500 + 5;
const LL INF = 5e9 + 5;
int kase, n, k, a[maxn];

bool isOK(LL max_sum) {
    int cnt = 0;
    LL sum = 0;
    for(int i = 0; i < n; i ++) {
        if(a[i] > max_sum) return 0;
        sum += a[i];
        if(sum > max_sum) cnt ++, sum = a[i];
        if(cnt > k) return 0;
    }
    if(sum) cnt ++;
    if(cnt > k) return 0;
    return 1;
}

void output(LL l) {
    vector<int> rev_ans;
    LL cur = 0;
    int cnt = 0;
    for(int i = n - 1; i >= 0; i --) {
        if(i + 1 == k - 1 - cnt) {  // 若剩下的数字量个剩下的分隔线数量相等
            cnt ++, rev_ans.push_back(-1);
            rev_ans.push_back(a[i]);
            continue;
        }
        cur += a[i];
        if(cur > l) cnt ++, rev_ans.push_back(-1), cur = a[i];  // -1代表分隔线
        rev_ans.push_back(a[i]);
    }
    for(int i = rev_ans.size() - 1; i >= 0; i --) {
        int ans = rev_ans[i];
        if(ans == -1) cout << "/";
        else cout << ans;
        cout << (i ? " " : "");
    }
    cout << endl;
}

int main() {
    cin >> kase;
    while(kase --) {
        cin >> n >> k;
        for(int i = 0; i < n; i ++) cin >> a[i];
        LL l = 0, r = INF;
        while(l < r) {
            LL mid = l + (r - l) / 2;
            if(isOK(mid)) r = mid;
            else l = mid + 1;
        }
        output(l);
    }
}

四、归纳

  • [最大值尽可能小]、[最小值尽可能大], 这类问题时,可以考虑二分答案。
  • 注意二分时判断OK后r等于多少,l等于多少不要搞错了。一般我们把范围想为[l, r]闭区间不容易错。所以不OK时 l=mid+1,因为不应该包含mid。此为,最好用l=1, r=2来验证一下是否会陷入死循环