题目描述
对于给定的一个长度为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。
输入输出格式
输入格式:
输入文件divide_b.in的第1行包含两个正整数N,M,第2行包含N个空格隔开的非负整数A[i],含义如题目所述。
输出格式:
输出文件divide_b.out仅包含一个正整数,即每段和最大值最小为多少。
输入输出样例
输入样例#1:
5 3 4 2 4 5 1
输出样例#1:
6
说明
对于20%的数据,有N≤10;
对于40%的数据,有N≤1000;
对于100%的数据,有N≤100000,M≤N, A[i]之和不超过10^9。
题解
因为题目中明确给出最小值最大,就容易想到是用二分答案来做(最小值最大或最大至最小)。
第一步确定二分边界:
n个数最多分成n段,那么当n == m时,max(a[i])就是所需答案,我们把它设为M,即答案最小值为M,所以在二分答案中的l(左边界)就等于M。
而当m == 1时,n个数就分成了1段,那么Σ(a[i])就是所需答案,我们把它设成sum,即答案最大值为sum,所以在二分答案中的r(右边界)就等于sum。
接下来关键的就是确定check函数怎么写了:
我们二分出mid=(l+r)/2,假设mid可以成为一个答案,我们所需要记录的就是当每段和(记作sum)的最大值<=mid时(即sum都需<=mid,),可以把整个序列分成几段(记作tot),最后一定要处理剩余的sum中的元素(忘写就GG),然后判断tot是否<=m(因为最大就是m)是返回1,否返回0。然后如果成立的话,我们就将答案尽可能缩小(即r=mid),否则就把答案扩大(l=mid+1)。最后的答案mid就被记在了r中,所以我们输出r就好了。
代码放一波:
1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 #define maxn 100005 5 using namespace std; 6 int n,m,a[maxn]; 7 bool check(int t) { 8 int tot=0,sum=0; 9 for(int i=1;i<=n;i++) { 10 if(sum+a[i]<t) sum+=a[i];//小于就累加使它尽可能大 11 else if(sum+a[i]==t) {//等于说明刚好满足答案,就开始分下一段 12 sum=0,tot++; 13 } else {//大于说明不能累加了,而且下一段的第一个元素就是当前元素 14 sum=a[i],tot++; 15 } 16 } 17 if(sum) tot++;//处理余下的元素 18 if(tot<=m) return 1; 19 return 0; 20 } 21 int main() { 22 scanf("%d%d",&n,&m); 23 int sum=0,M=0; 24 for(int i=1;i<=n;i++) { 25 scanf("%d",&a[i]); 26 M=max(M,a[i]); 27 sum+=a[i]; 28 } 29 if(n == m) { 30 printf("%d",M); 31 return 0; 32 } 33 int l=M,r=sum; 34 while(l<r) { 35 int mid=(l+r)>>1; 36 if(check(mid)) r=mid; 37 else l=mid+1; 38 } 39 printf("%d",r); 40 return 0; 41 }
ps:每日一水有益于身心健康哦ovo