今天在网络冲浪时遇到了这么一道题目
数列分段
对于给定的一个长度为N(N≤100000)的正整数数列 ai,现要将其分成 M(M≤N)段,
并要求每段连续,且每段和的最大值最小。关于最大值最小:
例如一数列 4 2 4 5 1 要分成 3 段。
将其如下分段:
[4 2][4 5][1]
第1段和为6,第2段和为9,第3段和为1,和最大值为9。将其如下分段:
[4][2 4][5 1]
第1段和为4,第2段和为6,第3段和为6,和最大值为6。
并且无论如何分段,最大值不会小于6。所以可以得到要将数列4 2 4 5 1 要分成 3 段,每段和
的最大值最小为 6。###输入格式:
第 1 行包含两个正整数 N,M。
第 2 行包含 N 个空格隔开的非负整数 ai,含义如题目所述。
数据保证所有ai在整型范围内。###输出格式:
一个正整数,即每段和最大值最小为多少。###输入样例:
5 3 4 2 4 5 1
###输出样例:
6
代码长度限制
64 KB
时间限制
4000 ms
内存限制
256 MB
栈限制
102400 KB
由于之前也没有学习过什么算法,打算通过这道题目简单了解一下写基础知识,于是认真的做了一下,具体过程如下
首先看到了这么一段代码
对于给定的一个长度为 n 的正整数数列 ai ,现要将其分成连续的若干段,并且每段和不超过 m(可以等于 m),问最少能将其分成多少段使得满足要求。
#include<iostream>
using namespace std;
int main()
{
int n,m;
int Thissum=0,count = 1;
int a[100000];
cin >> n;
cin >> m;
for (int i = 0; i < n; i++)
cin >> a[i];
for (int j = 0; j < n; j++)
{
Thissum += a[j]; //记录当前和
if (Thissum >m)
{
count += 1;
Thissum =a[j];/*当前和大于m时, 令当前和等于已处理的最后一个数
从这个数开始往后处理*/
}
}
cout << count << endl;
return 0;
}
它的题目与我的并不相同,但是思路很有意思。一个循环读入数据,然后从第一位开始遍历整个数组,定义一个当前和,每遍历一个新元素都将这个元素累加到当前和,并在累加后进行判断,如果当前和大于所要求的最大值m,则计数器加1表明这一段已经结束了该重新来计算新的一段,并且令当前和等于刚刚遍历到的这个元素。假如当前和小于或等于则此段尚未结束,无需另起一段,继续遍历下一个元素。直到遍历完全部元素后输出计数器的值。
对于本道题目,采用二分的方法,首先是数据的预处理,分别读入n和m的值然后将设计循环读入数组,在读入数据的过程中计算出数组元素的最大值和整个数组的总值。分别作为二分查找的两个端点值,当是左侧端点时,数组被分成数组的容量段,当为右侧值时数组被分成一段,从中间值出发,当假设过程的count小于预设值m时证明数组拆分的段数少了,即实际和应小于现在的中间值,此时将中间值减1作为新的右边界;假设count=m时证明在当前中间值情况下满足拆分段数,但题目要求需要寻找最大值,于是将右边界作为试探边界,将中间值减1作为右边界试探最大值;假设count大于m,则分段数大于预设值,此时假定的中间值小于实际值,将中间值加1作为左边界。如此重复该步骤直到二分查找到所求答案。输出可信边界左边界。
代码实现如下
#include <bits/stdc++.h>
using namespace std;
int n,m,a[100005],l,r,mid,mmax,sum;
int main(){
cin>>n>>m;
for(int i=0;i<n;i++){
cin>>a[i];
if(mmax<a[i]){
mmax=a[i];
}
sum+=a[i];
}
l=mmax;
r=sum;
while(l<r){
mid=(l+r)/2;
int thissum=0;
int count=0;
for(int j=0;j<n;j++){
thissum+=a[j];
if(thissum>mid){
thissum=a[j];
count++;
}
}
if(count>m){
l=mid+1;
}else{
r=mid-1;
}
}
cout<<l;
}