题目链接
一本通OJ:http://ybt.ssoier.cn:8088/problem_show.php?pid=1436。
我的OJ:http://47.110.135.197/problem.php?id=4461。
题目
题目描述
对于给定的一个长度为N的正整数数列A[i],现要将其分成M(M≤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],含义如题目所述。
输出
仅包含一个正整数,即每段和最大值最小为多少。
样例输入
5 3
4 2 4 5 1
样例输出
6
数据范围
N ≤ 1000,000,M ≤ N,1 ≤ Ai ≤ 1000000。
P.S. 一本通 OJ 是没有给出数据范围,我是经过多次测试出的数据范围,主要是 N 的大小。
分析
题意分析
从题目的描述中,我们可以知道,需要从 max(A[i]) 到 中找到一个合适的长度,对数列 A 进行分段。自然想到使用二分查找。
使用输入样例,我们来模拟一下二分查找的过程。
left | right | mid | 分段情况 | 分析 |
5 | 16 | 10 | [4 2 4] [5 1] | 不满足题目要求,mid 偏大,要变小 |
5 | 9 | 7 | [4 2] [4] [5 1] | 满足题目要求,mid 可能偏小,要变大 |
5 | 6 | 5 | [4] [2] [4] [1] | 满足题目要求,mid 可能偏小,要变大 |
6 | 6 | 6 | [4 2] [4] [5 1] | 满足题目要求,mid 可能偏小,要变大 |
6 | 5 | 结束二分查找,最佳的答案是 6 。 |
算法思路
一个标准的二分查找模板。
二分查找
1、left = max(A[i]),right = sum(A[1], A[2], ......, A[n]) 这个数据范围,进行二分查找
2、以 mid 为每段和的最大值,也就是每次分段和都不能超过 mid。
3、如果分段数 cnt > m,说明这个 mid 偏小,那么 mid 还可以再大一点,也就是左边界(left)移动到 mid+1;如果分段数 cnt ≤ m,说明这个 mid 偏大,那么 mid 还可以再小一点,也就是右边界(right)移动到 mid-1。注意需要记录下这个 mid。
AC参考代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e6+2;
int data[MAXN];
bool check(int x, int n, int m) {
int cnt=1;
int len=0;//当前长度
for (int i=1; i<=n; i++) {
if (len+data[i]<=x) {
len += data[i];
} else {
cnt++;
len = data[i];
}
}
return cnt<=m;
}
int main() {
int n,m;
cin>>n>>m;
int i;
int left=0;
int right=0;
for (i=1; i<=n; i++) {
cin>>data[i];
left = max(left, data[i]);
right += data[i];
}
int mid;
int ans;
while (right>=left) {
mid = left+((right-left)>>1);
//按照mid为最大值分段进行分段处理
if (true==check(mid, n, m)) {
//满足题目要求,mid 可能偏小,要变大
ans = mid;//记录下这个分段值
right = mid-1;
} else {
//不满足题目要求,mid 偏大,要变小
left = mid+1;
}
}
cout << ans << endl;
return 0;
}