题目描述
对于给定的一个长度为N的正整数数列 A-iA−i ,现要将其分成 M(M≤N)M(M≤N) 段,并要求每段连续,且每段和的最大值最小。
关于最大值最小:
例如一数列 4 2 4 5 142451 要分成 33 段
将其如下分段:
[4 2][4 5][1][42][45][1]
第一段和为 6 ,第 2 段和为 9 ,第 3 段和为 1 ,和最大值为 9 。
将其如下分段:
[4][2 4][5 1][4][24][51]
第一段和为 4 ,第 2 段和为 6 ,第 3 段和为 6 ,和最大值为 6 。
并且无论如何分段,最大值不会小于 66 。
所以可以得到要将数列 4 2 4 5 142451 要分成 3 段,每段和的最大值最小为 6 。
输入输出格式
输入格式:
第 1 行包含两个正整数N,M。
第 22行包含 NN 个空格隔开的非负整数 Ai ,含义如题目所述。
输出格式:
一个正整数,即每段和最大值最小为多少。
输入输出样例
说明
对于 20\%20% 的数据,有 N≤10N≤10 ;
对于 40\%40% 的数据,有 N≤1000N≤1000 ;
对于 100\%100% 的数据,有 N≤100000,M≤N, Ai,Ai 之和不超过 10^9 。
这个题时一个典型的二分答案(最大的最小、最小的最大)
首先根据题意,我们确定段的最大值一定在整个数组的最大值和10^9之间,因此做如下定义
l = maxn, r = 1000000000;
然后我们确定好一个mid = (l+r)>> 1,表示最大值为mid时是否可行;
下面就进行二分的正常过程,判断
然后遍历整个数列,记录下一个tot,表示这个数列中最大值不超过mid时,需要分成的段数,用now来代表当前段的和
now+a[i]<mid时:可以继续加,这个段还没有结束
now+a[i]==num时:说明当前段已经达到了最大值,记下这个段(tot++),now=0,以便分析下一个段
now+a[i]>num时:说明这个段加上a[i]之后超过mid,就不能加上这个a[i],那么这个段已经计算完(tot++),a[i]算到下一个段内
如果tot<m,说明要达到当前的段数m,mid的值必须要降低,这样才能使段数增多,收缩右边界;
否则,改变左边界。
附上代码(亲测AC)
1 #include<iostream> 2 #include<cmath> 3 using namespace std; 4 const int MAXN=100001; 5 int maxn = -1; 6 int n, m, a[1000010]; 7 int l = maxn, r = 1000000000; 8 int judge(int num){ 9 int now = 0; 10 int tot = 0; 11 for(int i=1;i<=n;i++){ 12 if(now+a[i]<num){ 13 now += a[i]; 14 } 15 else if(now+a[i]==num){ 16 now = 0; 17 tot++; 18 } 19 else if(now+a[i]>num){ 20 now = a[i]; 21 tot++; 22 } 23 } 24 if(now) tot++; //如果还有剩余的一个值,那么这个单独算一个段 25 if(tot>m)return 0; 26 else return 1; 27 28 } 29 30 int main(){ 31 cin >> n >> m; 32 for(int i=1;i<=n;i++) { 33 cin >> a[i]; 34 maxn = max(a[i], maxn); 35 } 36 int l = maxn, r = 1000000000; 37 if(n==m){ 38 cout << maxn; 39 return 0; 40 } 41 while(l<r){ 42 int mid = (l+r) >> 1; 43 if(judge(mid)==1){ 44 r = mid; 45 } 46 else{ 47 l = mid + 1; 48 } 49 } 50 cout << r; 51 return 0; 52 }