题目描述
在印刷术发明之前,复制一本书是一个很困难的工作,工作量很大,而且需要大家的积极配合来抄写一本书,团队合作能力很重要。当时都是通过招募抄写员来进行书本的录入和复制工作的, 假设现在要抄写 \(m\) 本书,编号为 \(1,2,3...m\) , 每本书有 \(1 \le x \le 100000\) 页, 把这些书分配给 \(k\) 个抄写员,要求分配给某个抄写员的那些书的编号必须是连续的,每本书只能被一个抄写员抄写。每个抄写员的速度是相同的,你的任务就是找到一个最佳的分配方案,使得所有书能够被抄完的前提下,每个抄写员所抄写的页数最少。
输入格式
在第一行中,有两个整数 \(m 和 k, 1<=k,m<=100000\) 。 在第二行中,有 \(m\) 个整数 \(x_i\) 用空格分隔。 所有这些值都为正且小于 \(100000\) 。
输出格式
输出一行数字,代表最佳的分配方案中,全部抄写完毕,抄写页数最多的抄写员所抄写的页数。
样例输入
9 3
100 200 300 400 500 600 700 800 900
样例输出
1700
题目分析
此题涉及算法:二分。
视频讲解地址:https://www.bilibili.com/video/av59514881/?p=3
这道题目是二分答案。
首先我们可以编写一个 bool check(int num)
函数来判断在分配给每个抄写员 \(num\) 页书时 \(k\) 个抄写员是否能够超写完 \(m\) 本书。如果可以,\(check(num)\) 返回 true
;否则,\(check(num)\) 返回 false
。
然后,我们会发现:
- 当 \(num = 0\) 时,\(check(0)\) 肯定返回
false
; - 当 \(num = \sum_{i=1}^nx_i\) 时,\(check(\sum_{i=1}^nx_i)\) 肯定返回
true
,因为此时只需要一个抄写员就可以抄写完 \(m\) 本书。
那么在 \(0\) 和 \(\sum_{i=1}^nx_i\) 之间肯定存在一个最小的 \(num\) 使得 \(check(num)\) 返回 true
。
我们可以发现:
- 如果 \(check(num)\) 返回
true
,那么 \(check(num+1)、check(num+2)、……、check(\sum_{i=1}^nx_i)\) 都返回true
; - 如果 \(check(num-1)\) 返回
false
,那么 \(check(num-2)、check(num-3)、……、check(0)\) 都返回false
。
所以如果我们以答案 num
为自变量,以答案是否成立(\(check(num)\) 返回的结果)为应变量,我们可以得到一个应变量随自变量变化的单调函数曲线。
实现代码如下:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100010;
int m, k; // m表示书有多少本,k表示抄写员的数量
long long a[maxn]; // a[i]表示第i本书的页数
// check函数用于判断分配给没个抄写员num页书的时候能否超写完
bool check(long long num) {
int id = 1; // id用于标记当前抄写员的编号,一开始id=1
long long tot = 0; // tot用于表示第id个抄写员目前已经抄写的书的页数
for (int i = 0; i < m; i ++) { // 循环遍历每一本书
if (num - tot >= a[i]) { // 如果第id个抄写员能够抄写第i本书,
tot += a[i]; // 将tot+=a[i],因为tot表示第id个抄写员目前抄写的书
}
else if (a[i] > num) // 如果a[i]>num,说明任何抄写员都抄写不了第i本书
return false;
else {
id ++; // id++,变成下一个抄写员的编号
tot = a[i]; // 更新下一个抄写员已经抄写的书的页数为a[i],因为他目前只抄写了第i本书
if (id > k) return false; // 如果id>k,说明k个抄写员已经不够用了,直接返回false
}
}
return true; // 返回true
}
int main() {
cin >> m >> k;
long long sum = 0;
for (int i = 0; i < m; i ++) {
cin >> a[i];
sum += a[i];
}
long long L = 0, R = sum, res; // L记录答案的左边界,R记录答案的右边界,res记录答案
while (L <= R) {
long long mid = (L + R) / 2LL;
if (check(mid)) { // 找到当前最优解
res = mid; // 更新答案
R = mid - 1; // 看看有没有更优解
}
else L = mid + 1; // 进有半部分找解
}
cout << res << endl; // 输出答案
return 0;
}