本题是一道典型的最大值最小化的例题。
题目要求将一段数列分成m段(m确定),求所有分法所产生的最大子段和中,最小的值。
最朴素的想法就是枚举所有分成m段的分法,将每种分法所产生的子段中和最大的值汇总起来,进行比较,从而找出最小的那一个。
在朴素的方法上进行进一步的升级,我们可以从小到大枚举子段和的最大值可能取到的值(在不考虑具体分成多少段的前提下),将每一次枚举到的情况进行判断,看看是否符合分成m段的要求。
对上述方法进行进一步的思考,枚举过程是否可以采用二分的方法?(即二分答案:用二分的方法去枚举最大值),为了判断是否可以使用二分的方法,我们需要去寻找一个判断条件,使得该条件可以将最大值的取值范围划分成左右两个部分,(在此之前有一个重要的思想————显而易见,分段越多子段最大值越小,分段越少子段最大值越大,最大值确定时分段数量可能不同,即一个最大值会对应一个区间范围内的分段数量情况)寻找判断条件:
假设区间中有一个点q(q即我们要求的最终答案,是未知的),当区间中的点x小于q时,那么x对应的分段数一定是大于等于q对应分段数的最小值;当x大于q时,x对应的分段数一定是小于等于q对应分段数的最大值。综上,我们在二分枚举x的时候,可以利用x对应的分段数与规定的分段数进行比较(利用贪心的方法可以求出x的最小分段数 –> 每次枚举x时都找其分段数尽可能的小的情况,从而可以求出在分段数相同时x值尽可能的小的情况<即我们的目的>),从而将取值范围划分成左右两个区间:
-
当分段数 > 指定分段次数时,说明指定的和太小,应该让和大点,才能让分段次数少点,因此进入右区间,left = mid + 1;
-
当分段数 = 指定分段次数时,此时分段数刚刚好,若是从小到大遍历,可直接退出,但是倘若是二分查找,得继续往左区间看有没有更小的分段次数满足要求,因此right = mid -1;
-
当分段数 < 指定分段次数时,说明此时指定的和太大,导致分段数太少,因此要让和小一点,因此进入左区间,right = mid -1
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 1e5 + 10;
int a[N];
int n, m;
bool check(int mid)
{
int res = 0, cnt = 1;
for(int i = 0; i < n; i ++)
{
if(res + a[i] <= mid) res += a[i];
else
{
cnt ++;
res = a[i];
}
}
if(cnt > m) return true;
else return false;
}
int bsearch(int l, int r)
{
while(l < r)
{
int mid = l + r >> 1;
//cout << l << " " << r << " " << mid << endl;
if(check(mid)) l = mid + 1;
else r = mid;
}
return l;
}
int main()
{
cin >> n >> m;
int l = -1, r = 0;
for(int i = 0; i < n; i ++)
{
scanf("%d", &a[i]);
r += a[i];
l = max(l, a[i]);
}
int ans = bsearch(l, r);
cout << ans << endl;
return 0;
}