题解:洛谷 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
【解题思路】

【算法标签】
《洛谷 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
浙公网安备 33010602011771号