【题目描述】
对于给定的一个长度为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
题解:
最小的部分和最大值一定是存在的(以下称其为“这个值”),只是我们还不知道。我们可以采用二分的方法逼近这个值,因为我们可以很容易地判断一个数是小于这个值还是大于这个值。
如果一个数比这个值小,那么无论如何怎么分,也不可能把数列分成m段,使得部分和的最大值小于等于该数。因为这个值本身就是将数列分成m段其中部分和的最小值。也就是说,不能成功划分数列的的数是比这个值小的。
如果一个数比这个值大,那么它就可以将数列分成m段,使得部分和的最大值小于等于该数。反过来说,能成功划分数列的数是大于这个值的数
#include<cstdio>
#include<iostream>
using namespace std;
int a[100010],sum[100010];
int n,m;
bool check(int max_sum)//是否存在一种划分方法,使得部分和的最大值小于等于max_sum
{
//初始化
int sum = 0;
int sum_section = 0;
for(int i = 1; i <= n; ++i)
{
sum += a[i];//累加
if(sum > max_sum)//如果超了
{
sum = a[i];//就赋成a[i]
sum_section++;//段数+1
}
}
sum_section++;//无论加上最后一个元素超出max_sum还是不超出,都要将段数+1
if(sum_section <= m)//如果段数小于等于m
return true;//可以,仔细想想这个逻辑
else//否则不可以
return false;
}
int main()
{
cin >> n >> m;//输入
int max_a = -1;//初始化
for(int i = 1; i <= n; ++i)
{
scanf("%d",&a[i]);
sum[i] = sum[i - 1] + a[i];//前缀和
max_a = max(max_a,a[i]);//数列中的最大值
}
int l = max_a,r = sum[n];//二分边界,当分成n段时,显然部分和的最大值就是max_a,当分成的段数小于n时,部分和的最大值开始增大,当分成1段时,部分和的最大值达到最大,即为数列的前n项和。如此分析,便得出了二分的边界
while(l < r)//二分模板
{
int mid = (l + r) / 2;//注意最大值最小化是这样的,而最小值最大化是(l + r + 1) / 2;
if(check(mid) == true)
r = mid;//保证答案的正确性
else
l = mid + 1;
}
cout << r << endl;//输出r
return 0;
}
---------------------
原文:https://blog.youkuaiyun.com/suntengnb/article/details/78045645