二分入门练习题6 最大值最小化 题解

题目描述

在印刷术发明之前,复制一本书是一个很困难的工作,工作量很大,而且需要大家的积极配合来抄写一本书,团队合作能力很重要。当时都是通过招募抄写员来进行书本的录入和复制工作的, 假设现在要抄写 \(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;
}

猜你喜欢

转载自www.cnblogs.com/zifeiynoip/p/11450631.html