题解 复制书稿

07yintaobo 2022-11-19 17:20:17 2022-12-03 11:00:31 8 返回题目

二分答案思想

二分,顾名思义要将数据分成两类,比如小于等于XX和大于XX两类。分类要求不重不漏且满足单调。

其实做这道题应该对二分查找有较深理解的,所以这里只是简述一下,接下来不再讲。

二分答案基于二分原理,即用二分的思想快速“猜出”最合适的答案。当且仅当:

二分解题思路

关于本题,可抽象为求将一个数列分成指定份时每份和的“最大值最小”的方案。

从“最大值最小”可以从两个角度看,首先“最大值最小”“最小值最大”一般是二分答案的典型(裸)特征,所以此题的二分思想并不复杂;

另一方面,由于此题显然是一个最优解问题,数据范围不大所以动态规划也是可行的。

下面讲此题的基本思路。

此题答案不是一个数,而是在 “抄写页数最多的人” 抄写的页数最少时的具体方案。当我们要求出这个方案时,是不是首先要得到这个最小值?所以可以直接先二分这个值。

最后考虑,我们如何验证答案是否可行?如前所述,由于我们假想已知这个“抄写页数最多者的页数”,可以使用贪心法。

由于答案本身是最大值,也就是不允许超过的,那么我们可以遍历整个数组,逐个取书给某人抄写,直到发现再抄就会超过上限时,就换下一个人来取。

这样,在遍历结束时,就可知道这个上限对应最少需要多少人抄写。如果这个数量超过了kk,即总人数不足抄写完全部的书,表示答案是不可行的;反之,如果这个值小于等于kk,这个答案就是可行的。

注意遍历的方向。根据上述内容容易看出,这个方法是优先先遍历的人抄写,也就是如果只需一部分人抄写,那么优先让先遍历的人抄得多些。

而对应题中是要求尽可能让前面的人少抄写(走后门?),同时本身数据是升序的(顺便一提,如果不是升序也需排序成升序),所以需要倒序遍历。

至此,我们已理出了二分答案的大部分框架。

二分代码实现

十分容易就写出了以下函数:

while (l + 1 < r) {
        int mid = (l + r) / 2;
        if (check(mid)) {
            r = mid;
        } else {
            l = mid;
        }
    }

一般二分答案重点就在于 函数。但由于我们上面已理清思路(有不明白可以多读几遍),写起来会发现并不太困难。

累计总共所需的人数, 保存当前的人抄写的总页数。由于一开始就有个人,初始化为

有一个细节要注意,由于我们计算时是不允许超过上限的,所以首先判定当前的人抄写此书是否超出上限,只有在确定不会超出时才进行累加。

bool check(int x) {
    int num = 1, t = 0;
    for (int i = m; i >= 1; i--) {
        if (t + a[i] > x)
            t = 0, num++;
        t += a[i];
    }
    return num <= k;
}

输出

现在,要如何从这个最小值求出具体的方案?

等等!是不是感觉似曾相识?的确,刚刚我们刚打完根据某个“答案”求出具体方案的方法。也就是函数中的那个倒序循环,正好可用于生成答案。

于是,现在我们设置数组表示各人负责书的起始编号和结束编号。注意,假设前面有部分人不必使用时存储的就是00 00,所以需要初始化为00。

那么我们在何时给这个数组赋值?显然是在“换人”时。也就是在的时候。

由于在此时表示第本已经不能给当前的人抄写了,所以;那么第ii本书应归下一人抄写,所以

注意,以上均是建立于从后往前遍历的基础上的,所以每个人的结束编号在开始编号之前确定。这里必须花一点时间确保理解。

最后注意一点,由于第一个人的结束编号(即书的总本数)和最后一个人的起始编号(即第一本)无法在循环中设定,所以最后还需增加;

小结一下,实际上我们完全可以直接复制中的部分,然后在判断处增加数组的处理,同时前后注意初始化即可获得一个完整的输出数组。

void write(int x) {
    int t = 0;
    ans[++ansl][1] = m;
    for (int i = m; i >= 1; i--) {
        if (t + a[i] > x)
            t = 0, ans[ansl][0] = i + 1, ans[++ansl][1] = i;
        t += a[i];
    }
    ans[ansl][0] = 1;
    for (int i = ansl; i >= 1; i--) {
        cout << ans[i][0] << " " << ans[i][1] << endl;
    }
    return;
}

总结

参考代码如下(建议开):

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 5e3 + 10;
int m, k, a[N];
int ans[N][2], ansl;
bool check(int x) {
    int num = 1, t = 0;
    for (int i = m; i >= 1; i--) {
        if (t + a[i] > x)
            t = 0, num++;
        t += a[i];
    }
    return num <= k;
}
void write(int x) {
    int t = 0;
    ans[++ansl][1] = m;
    for (int i = m; i >= 1; i--) {
        if (t + a[i] > x)
            t = 0, ans[ansl][0] = i + 1, ans[++ansl][1] = i;
        t += a[i];
    }
    ans[ansl][0] = 1;
    for (int i = ansl; i >= 1; i--) {
        cout << ans[i][0] << " " << ans[i][1] << endl;
    }
    return;
}
signed main() {
    cin >> m >> k;
    if (m == 0 && k == 0) {
    	return 0;
	}
    int l = 1, r = 1e9;
    for (int i = 1; i <= m; i++) {
        cin >> a[i];
    }
    while (l + 1 < r) {
        int mid = (l + r) / 2;
        if (check(mid)) {
            r = mid;
        } else {
            l = mid;
        }
    }
    if (check(l)) {
        write(l);
    } else {
        write(r);
    }
    return 0;
}

希望能对大家有一些帮助,谢谢!

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