题目描述
对于给定的一个长度为 N 的正整数数列 A ,现要将其分成 M 段,并要求每段连续,且每段和的最大值最小。
例如,将数列 4 2 4 5 1 要分成 3 段:
若分为 [4 2] [4 5] [1],各段的和分别为 6,9,1 ,和的最大值为 9;
若分为 [4] [2 4] [5 1],各段的和分别为 4,6,6 ,和的最大值为 6;
并且无论如何分段,最大值不会小于 66。
所以可以得到要将数列 4 2 4 5 1 要分成 3 段,每段和的最大值最小为 6 。
输入格式
第 1 行包含两个正整数 N,M;
第 2 行包含 N 个空格隔开的非负整数 Ai,含义如题目所述。
输出格式
仅包含一个正整数,即每段和最大值最小为多少。
样例
输入数据 1
5 3
4 2 4 5 1
Copy
输出数据 1
6
Copy
数据范围与提示
对于 20%的数据,有 N≤10;
对于 40% 的数据,有 N≤1000;
对于 100%的数据,有 N≤10^5,M≤N,Ai 之和不超过 10^9 。
反思总结:
这是二分题
一般求最大化最小值情况都是二分题目。
这道题需要想到一些特定的数据,这题还是有很多细节化的东西需要很小心和一些好习惯。
思路:自己假设一个最大值,通过分段数的多少判断最大值是大了还是小了。
这题的难点在于,如何分段和如何确定最大值。
1、我们可以通过二分的方法确定最大值
2、根据最大值进行分段
细节处理:
这道题还是有很多细节的
1、初始化 l 和 r 的时候,不可以随便取边界值,因为 l 的最小值是数列中的最大值,所以,l 的初始化值为数列中的最大值,r 最大的时候就是数列中所有数字的和。
如果随便初始化 l 和 r 有可能会出现结果小于答案的情况,
如:5 3
4 2 4 5 3000
这个时候会输出 9 ;
这个时候答案应该是3000,如果随便初始化 l 和 r ,因为段数少于题目要求,程序会认为,现在的最大值太大了,中间值就会不断的缩小,直到段数大于等于题目的要求为止。
2、段数如何求的问题:
用一个变量将同一段的数加起来,因为我们假设最大值是某个数,所以,在同一段内的和不能大于我们所假设的最大值(mi),可以等于。
判断数是否在同一段:通过判断同一段内数的和加上现在这个数的值大于我们假设的最大值吗,如果大于,则这个数为下一段的第一个数,小于就可以归为同一段。
3、最后答案输出 l 还是 r 的问题:
因我写的代码 l 和 r 是相差 1 的,所以需要特判一下输出 l 还是 r
我是通过再判断一次分段数来判断的,符合条件的就输出。
4、尽量别写 l = mid + 1 和 r = mid + 1 !!!
不过这样写也需要判断一下输出 l 还是 输出 r 。
为啥不写left=mid+1和right=mid-1
因为有些情况下会不能用
比如在数列
a[]={0 1 3 4,5}
找大于2的第一个位置
当你right=4,left=0;
则mid=2; A[mid]>2 所以 若r=mid-1=1你就错过去了,除非你用if判断一下。
#include <stdio.h>
#include <math.h>
#include <algorithm>
#include <iostream>
#include <string.h>
#include <queue>
#include <stack>
#include <map>
#include <set>
#include <vector>
using namespace std;
#define ll long long
const ll N=1000000007;
int n,m;
ll a[100007],mia,sum=0;
int main() {
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++) {
scanf("%lld",&a[i]);
mia=max(a[i],mia);
sum+=a[i];
}
ll l=mia,r=sum;
while(l+1<r) {
ll mi=(l+r)>>1;
ll temp=0;
int ans=1;
for(int i=1; i<=n; i++) {
temp+=a[i];
if(temp>mi) {
ans++;
temp=a[i];
}
}
if(ans>m)
l=mi;
else
r=mi;
}
ll temp=0;
int ans=1;
for(int i=1; i<=n; i++) {
temp+=a[i];
if(temp>l) {
ans++;
temp=a[i];
}
}
if(ans>m)
printf("%lld\n",r);
else
printf("%lld\n",l);
return 0;
}