题目描述
对于给定的一个长度为N的正整数数列 A 1 ∼ N A _{1∼N} A1∼N,现要将其分成 M ( M ≤ N ) M(M≤N) M(M≤N)段,并要求每段连续,且每段和的最大值最小。
关于最大值最小:
例如一数列 4 2 4 5 1 4\ 2\ 4\ 5\ 1 4 2 4 5 1 要分成 3 段。
将其如下分段:
[ 4 2 ] [ 4 5 ] [ 1 ] [4\ 2][4\ 5][1] [4 2][4 5][1]
第一段和为 6,第 2 段和为 9,第 3 段和为 1,和最大值为 9。
将其如下分段:
[ 4 ] [ 2 4 ] [ 5 1 ] [4][2\ 4][5\ 1] [4][2 4][5 1]
第一段和为 4,第 2 段和为 6,第 3 段和为 6,和最大值为 6。
并且无论如何分段,最大值不会小于 6。
所以可以得到要将数列 4 2 4 5 1 4\ 2\ 4\ 5\ 1 4 2 4 5 1 要分成 3 段,每段和的最大值最小为 6。
输入格式
第 1 行包含两个正整数 N,M。
第 2 行包含 N 个空格隔开的非负整数 A i A_i Ai,含义如题目所述。
输出格式
一个正整数,即每段和最大值最小为多少。
输入输出样例
输入
5 3
4 2 4 5 1
输出
6
说明
对于 20 % 20\% 20% 的数据, N ≤ 10 N\leq 10 N≤10。
对于 40 % 40\% 40% 的数据, N ≤ 1000 N\leq 1000 N≤1000。
对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 1 0 5 1\leq N\leq 10^5 1≤N≤105 , M ≤ N M\leq N M≤N, A i < 1 0 8 A_i < 10^8 Ai<108, 答案不超过 1 0 9 10^9 109 。
思路分析(二分+贪心)
-
简化题目为:
将 n 个数分成相邻的 m 组,每组元素都是连续的(同时也不能为空)。求在所有的分法中,使得每组和的 最大值最小的值是多少? -
我们先想一个简单的问题:假定每组数和的最大值不超过v ,是否存在把数组分 为不多于m 段的方法? 不超过v 的前提下,每组数尽量多就行。
-
转换思路:每组数和最大值不超过v,求解至少分多少组。将这个值与 m 比较。 如果组数小于m组,则符合条件。假如蓝色是满足的v值,红色是不满足的,那交界处(v的最小值)就是我们要求的结果(每段和最大值最小)。
-
那么交界处的v值怎么求呢?二分
AC的C++代码
#include<iostream>
#include<algorithm>
using namespace std;
int a[100005],n,m,le,ri,mid;
//函数check判断v值是否满足条件
//返回值:若v满足条件,返回 1
bool check(int v)
{
int num = 0,sum = 0;
for(int i = 0;i < n;i++)
{
if(sum+a[i] <= v) // 组内所有数的和没有超过v
sum += a[i];
else //若加上a[i]超过v,所以从a[i]开始重新算作新的一组
sum=a[i],num++;
}
return num < m;
}
int main()
{
cin>>n>>m;
for(int i = 0;i < n;i++)
{
cin>>a[i];
le = max(le,a[i]); //每段和最大值最小(分为n组)
ri += a[i]; //每段和最大值最大(分为1组)
}
while(le <= ri)
{
mid = (le + ri)/2;
if(check(mid))
ri = mid-1; //v=mid时,组数比m小,那么我们要使组数变大,那么v值就要变小,继续在[l,mid-1]寻找
else
le = mid+1;
}
cout<<le;
return 0;
}