复制书稿题解

jhhk77 2020-11-03 21:20:31 2020-12-04 20:05:41 12 返回题目

题目大意

个数分成 段,使每段总和的最大值最小。

分析

个数分别为

若我们可以假设这个最大值最小为 ,则 最小为数列中的最小值 ,也不可能超过 个数的总和 ,所以 ,由于 足够小,所以我们可以在 暴力枚举

有了 ,接下来就要验证 是否合理或者达到最小。我们定义 函数。开辟一个数组 用于记录每段的开头与结尾。定义 用于记录当前的子段的值, 用于记录目前已分的段数。

考虑到在最大值相同时,前面的子段的和达到最小,所以循环要从 。假如 ,说明 已达上限,那么记录右端点 ,同时新开一段,即 ,再记录新子段的左端点 记得把 变为 。否则

具体 函数代码如下:

bool check(int x){
    int t[501][3];
    memset(t, 0, sizeof(t));
    t[1][2] = m; //最后一个子段的右端点是m
    int n = 1, total = a[m]; //最初有1个子段,且已经选取了一个数,所以total=a[m]
    for(int i = m - 1;i >= 1;--i){
        if(total + a[i] > x){ //达到上限x
            t[n][1] = i + 1; //记录右端点
            n++;
            t[n][2] = i; //记录左端点
            total = a[i]; //初始值即目前选取的这个数
        }
        else{
            total += a[i]; //未达上限,继续挑数
        }
    }
    t[n][1] = 1; //最前一个的子段的左端点端点为1
    if(n > k){
        return 0; //选取的子段个数不可能比k大
    }
    for(int i = 1;i <= n;i++){
        ans[n + 1 - i][1] = t[i][1];
        ans[n + 1 - i][2] = t[i][2];
    } //用ans数组记录左右端点
    return 1;
}

其实最大值 也可不枚举,用二分答案会更快。如下:

while(s <= e){
    int mid = (s + e) / 2;
    if(check(mid)){
        e = mid - 1;
    }
    else{
        s = mid + 1;
    }
}

其中最初

AC代码

#include <bits/stdc++.h>
using namespace std;
int m, k, a[501];
int ans[501][3];
bool check(int x){
    int t[501][3];
    memset(t, 0, sizeof(t));
    t[1][2] = m;
    int n = 1, total = a[m];
    for(int i = m - 1;i >= 1;--i){
        if(total + a[i] > x){
            t[n][1] = i + 1;
            n++;
            t[n][2] = i;
            total = a[i];
        }
        else{
            total += a[i];
        }
    }
    t[n][1] = 1;
    if(n > k){
        return 0;
    }
    for(int i = 1;i <= n;i++){
        ans[n + 1 - i][1] = t[i][1];
        ans[n + 1 - i][2] = t[i][2];
    }
    return 1;
}
int main() {
    scanf("%d%d", &m, &k);
    int s = INT_MAX, e = 0;
    for (int i = 1; i <= m; ++i) {
        scanf("%d", &a[i]);
        s = min(s, a[i]); //选取最小值,子段和最小值必定大于等于数组中最小值
        e += a[i]; //求和,子段和最大值必定小于等于所有数总和
    }
    while (s <= e) {
        int mid = (s + e) / 2;
        if (check(mid)) {
            e = mid - 1;
        } else {
            s = mid + 1;
        }
    }
    for (int i = 1; i <= k; ++i) {
        printf("%d %d\n", ans[i][1], ans[i][2]);
    }
    return 0;
}

暴力枚举只用把

while (s <= e) {
    int mid = (s + e) / 2;
    if (check(mid)) {
        e = mid - 1;
    } else {
        s = mid + 1;
    }
}

改为

for(int i = s;i <= e;i++){
    if(check(i)){
        break;
    }
}

也可

{{ vote && vote.total.up }}