二分答案思想
二分,顾名思义要将数据分成两类,比如小于等于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;
}
希望能对大家有一些帮助,谢谢!