Description
对于给定的一个长度为N的正整数数列A[1…N],现要将其分成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。
Input
第 1 行包含两个正整数 N,M。
第 2 行包含 N 个空格隔开的非负整数 Ai,含义如题目所述。
Output
一个正整数,即每段和最大值最小为多少。
Sample Input 1
5 3
4 2 4 5 1
Sample Output 1
6
Hint
对于 100% 的数据,1≤ N≤ 100,000,M≤N,A[i]≤ 10^8
答案不超过10^9
Time Limit
1000MS
Memory Limit
256MB
分析:
初看这道题有个误区,会惯性思维去想,数列分段和的最大值依赖于数列的分段的方法,必须先知道数列如何分段才能计算出所有分段和的最大值,如果这样去想,此题就复杂了。
实际上,数列分段和的最大值可以决定这个数列至少能分成几段,当每次分段都分到极限(再往下多加一个元素就会超过分段和最大值),这样所得的分段数就是最小值。想明白了这一点,这道题就有突破口了。
我们可以对数列分段和的最大值二分再进行枚举,二分的逻辑是:如果当前最大值下的最小分段数比欲分段数还大,就说明最大值太小了,可行答案应该更大;如果当前最大值的最小分段数不大于欲分段数,就说明这是一个可行答案,并且可以再试探更小的答案。
答案区间端点的选取理由在代码注释里。另外,此题让我明白二分写对的关键,除去区间端点更新和退出循环的条件那些老生常谈云云,要想找到(不遗漏)答案,必须明确什么情况下的解是可行解,并及时记录答案。这道题里,我本来只在temp==M的时候才记录答案,导致忽略了相当一部分可行解,最终没找到正确答案,oj评判结果为PA。后来发现其实temp<M的时候的答案也是可行解,也应该记录,在修改记录答案的逻辑后,果然AC了。
参考代码:
#include<stdio.h>
#include<algorithm>
long long int N,M;//数列长度,分段数
long long int a[100000];//数列
long long int *max,sum=0,ans=0;//数列最大值,数列和,答案
//计算分段和不超过x时,最少能分几段
long long int cnt(long long int x)
{
long long int sum=0,count=0;
for(long long int i=0;i<N;i++)
{ //如果加上a[i]没超过x,a[i]就仍属于当前分段
//否则a[i]作为新分段的开头,并及时更新分段数
sum+a[i]<=x?sum+=a[i]:(sum=a[i],++count);
//注意":"后的括号不能去,否则该行相当于
//(sum+a[i]<=x?sum+=a[i]:sum=a[i]),++count;
//每每执行","前的表达式,都会继续执行其后的表达式
}
//始终会少计一个分段,补回来
++count;
return count;
}
int main()
{
scanf("%lld%lld",&N,&M);
for(long long int i=0;i<N;i++)
{
scanf("%lld",a+i);
//顺便把数列和sum算了
//因为要找最小答案,所以给ans赋最大值
ans=sum+=a[i];
}
max=std::max_element(a,a+N);//找数列最大值
//左开右开写法,答案区间为[*max,sum]
//为了保证值为*max的元素能被分段,答案区间最小值是*max
//为了保证数列至少能被分成一段,答案区间最大值是sum
//temp记录数列分段和不超过mid的情况下最小分段数
long long int left=*max-1,right=sum+1,mid,temp;
while(left+1!=right)
{
mid=left+((right-left)>>1);
temp=cnt(mid);
//mid长度下最小分段数比M大,此时无法满足分成M段
//必然不是答案,且答案应在右区间
if(temp>M) left=mid;
//mid长度下最小分段数不大于M,是可行答案
//因为要找最小答案,故mid和之前搜索的答案ans相比较
//判断mid是不是更优解
else if(temp<=M){
ans>mid?ans=right=mid:right=mid;
}
}
printf("%lld",ans);
return 0;
}