题解:洛谷 P1281 [CERC1998] 书的复制

【题目来源】

洛谷:[P1281 CERC1998] 书的复制 - 洛谷

【题目描述】

大多数人的错误原因:尽可能让前面的人少抄写,如果前几个人可以不写则不写,对应的人输出 0 0

不过,已经修改数据,保证每个人都有活可干。

现在要把 \(m\) 本有顺序的书分给 \(k\) 个人复制(抄写),每一个人的抄写速度都一样,一本书不允许给两个(或以上)的人抄写,分给每一个人的书,必须是连续的,比如不能把第一、第三、第四本书给同一个人抄写。

现在请你设计一种方案,使得复制时间最短。复制时间为抄写页数最多的人用去的时间。

【输入】

第一行两个整数 \(m,k\)

第二行 \(m\) 个整数,第 \(i\) 个整数表示第 \(i\) 本书的页数。

【输出】

\(k\) 行,每行两个整数,第 \(i\) 行表示第 \(i\) 个人抄写的书的起始编号和终止编号。 \(k\) 行的起始编号应该从小到大排列,如果有多解,则尽可能让前面的人少抄写。

【输入样例】

9 3
1 2 3 4 5 6 7 8 9

【输出样例】

1 5
6 7
8 9

【解题思路】

image

【算法标签】

《洛谷 P1281 书的复制》 #动态规划DP# #贪心# #二分# #ICPC# #1998# #CERC#

【代码详解】

#include <bits/stdc++.h>
using namespace std;
const int N = 505;
int m, k, l, r;
int a[N], idx[N];

// 检查能否在最大值为x的条件下分成不超过k段
bool check(int x)
{
    int sum = 0, res = 0;  // sum: 当前段的和, res: 当前段数
    
    for (int i = 1; i <= m; i++) {
        if (sum + a[i] > x) {  // 如果加上当前元素超过x
            res++;             // 开始新的一段
            sum = a[i];        // 新段从当前元素开始
        } else {
            sum += a[i];       // 继续当前段
        }
    }
    
    if (sum) res++;  // 处理最后一段
    
    return res <= k;  // 段数是否不超过k
}

int main()
{
    // 输入元素个数m和段数k
    cin >> m >> k;
    
    // 读取数组元素,同时初始化二分搜索边界
    for (int i = 1; i <= m; i++) {
        cin >> a[i];
        l = max(l, a[i]);  // 左边界:最大元素值
        r += a[i];         // 右边界:所有元素和
    }
    
    // 二分查找最小的最大值
    while (l < r) {
        int mid = (l + r) / 2;
        if (check(mid))
            r = mid;     // mid可行,尝试更小的值
        else
            l = mid + 1; // mid不可行,需要更大的值
    }
    
    // 此时l是最小的最大段和
    
    // 从后往前划分段,使得前面的段尽可能长
    int sum = 0, cur = 0;  // sum: 当前段和, cur: 段数
    for (int i = m; i >= 1; i--) {
        if (sum + a[i] > l) {  // 如果加上当前元素超过l
            idx[++cur] = i + 1;  // 记录新段的开始位置
            sum = a[i];         // 新段从当前元素开始
        } else {
            sum += a[i];        // 继续当前段
        }
    }
    
    if (sum) idx[++cur] = 1;  // 记录最后一段的开始位置
    
    // 输出每一段的起止位置
    for (int i = cur; i >= 1; i--) {
        cout << idx[i] << " ";  // 段起始位置
        
        if (i == 1)
            cout << m;          // 最后一段结束位置是m
        else
            cout << idx[i-1] - 1;  // 当前段结束位置是下一段开始-1
        
        cout << endl;
    }
    
    return 0;
}

【运行结果】

9 3
1 2 3 4 5 6 7 8 9
1 5
6 7
8 9
posted @ 2026-03-14 22:09  团爸讲算法  阅读(1)  评论(0)    收藏  举报