P1182 数列分段 Section II
题目描述
对于给定的一个长度为 $N$ 的正整数数列 $A_{1\sim N}$,现要将其分成 $M$($M\leq N$)段,并要求每段连续,且每段和的最大值最小。
关于最大值最小:
例如一数列 $4\ 2\ 4\ 5\ 1$ 要分成 $3$ 段。
将其如下分段:
$$[4\ 2][4\ 5][1]$$
第一段和为 $6$,第 $2$ 段和为 $9$,第 $3$ 段和为 $1$,和最大值为 $9$。
将其如下分段:
$$[4][2\ 4][5\ 1]$$
第一段和为 $4$,第 $2$ 段和为 $6$,第 $3$ 段和为 $6$,和最大值为 $6$。
并且无论如何分段,最大值不会小于 $6$。
所以可以得到要将数列 $4\ 2\ 4\ 5\ 1$ 要分成 $3$ 段,每段和的最大值最小为 $6$。
输入格式
第 $1$ 行包含两个正整数 $N,M$。
第 $2$ 行包含 $N$ 个空格隔开的非负整数 $A_i$,含义如题目所述。
输出格式
一个正整数,即每段和最大值最小为多少。
输入输出样例 #1
输入 #1
5 3
4 2 4 5 1
输出 #1
6
说明/提示
对于 $20%$ 的数据,$N\leq 10$。
对于 $40%$ 的数据,$N\leq 1000$。
对于 $100%$ 的数据,$1\leq N\leq 10^5$,$M\leq N$,$A_i < 10^8$, 答案不超过 $10^9$。
思路分析 + 代码实现
题目求的是“最大的最小”,所以采用二分答案的方法。对于每个 $mid$ ,检查是否能将数组分成不超过 $M$ 段,使得每段和不超过 $mid$。如果可以,说明 $mid$ 是一个可行解,尝试更小的 $mid$ ;如果不行,说明 $mid$ 太小,需要增大 $mid$ 。
二分查找的核心是判断是否能将数组分成 $cnt <= M$ 段,使得每段的和都不超过 $mid$ 。我们用变量 $acc$ 累计本段的和,当 $acc + a[i] > mid$ 时,分出一段,让 $a[i]$ 在新一段的开头,并更新 $cnt$ 和 $acc$ 。但是!有一个细节要注意,这个逻辑能正确工作,有一个隐含的前提: $mid$ 必须大于等于数组中的最大元素 $ma$ 。如果 $mid < ma$,那么数组中存在至少一个元素 $a[i] = ma$,它无法被任何一段容纳(因为 $a[i] > mid$),在这种情况下,判断的逻辑就失效了。所以在读入的过程中记录 $ma$ ,二分时将 $l$ 设为 $ma$ 即可解决。代码如下,时间复杂度 $O(n \log n)$ 。
#include <bits/stdc++.h>
using namespace std;
constexpr int MAXN = 1e5 + 5;
int a[MAXN];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N, M, ma = 0;
cin >> N >> M;
for(int i = 0; i < N; i++) {
cin >> a[i];
ma = max(ma, a[i]);
}
int l = ma, r = 1e9, ans;
while (l <= r) {
int mid = (l + r) / 2, cnt = 1, acc = 0;
for(int i = 0; i < N; i++) {
int t = a[i] + acc;
if (t > mid) {
cnt++;
acc = a[i];
} else acc = t;
}
if (cnt <= M) {
ans = mid;
r = mid - 1;
} else l = mid + 1;
}
cout << ans;
return 0;
}