问题描述
把一个包含n个正整数的序列划分成m个连续的子序列。设第i个序列的各数之和为S(i),求所有S(i)的最大值最小是多少?
- 例子:
序列1 2 3 2 5 4划分为3个子序列的最优方案为 1 2 3 | 2 5 | 4,其中S(1),S(2),S(3)分别为6,7,4,那么最大值为7;
如果划分为 1 2 | 3 2 | 5 4,则最大值为9,不是最小。
解题思路
我们对问题做一些转化:
在一次划分中,求一个x,使得x满足:对任意的S(i),都有S(i)<=x;这个条件保证了x是所有S(i)中的最大值。我们需要求的就是满足该条件的最小的x。
有了这个思路之后,我们继续分析如何找到这个x,首先,可以知道的是,max <= x <= sum。
接下来先是最朴素的想法:枚举每一个x,贪心地每次从左向右尽量多划分元素,但是S(i)不能超过x,而且划分的子序列个数不能超过m个(即所用划分线不能超过m-1条)
以上方法当然可行,但是每个x都遍历一次太浪费时间了。
问题经过转化,现在变成了在[max, sum]中间查找一个满足条件的x,查找的问题,相信大家对二分搜索并不陌生。这个时候,用二分搜索的思想来求x,效率一下子就上来了。
代码
int max = 元素中的最大值;
int sum = 所有元素之和;
//是否能把序列划分为每个序列之和不大于x的m个子序列
bool is_part(int x)
{
//每次往右划分,划分完后,所用的划分线不大于m-1个即可
int line = 0, s = 0;
for(int i = 0; i < n; i++)
{
if(s+A[i] > x) //和大于x,不能再把当前元素加上了
{
line++; //加一条分隔线
s = A[i];
if(line > m-1) //分隔线已经超过m-1条
return false;
}
else
{
s+=A[i]; //把当前元素与前面的元素连上,以便尽量往右划分,贪心到底
}
}
return true;
}
int binary_solve()
{
int l = max, r = sum;
while (l < r)
{
int m = l + (r-l)/2;
if (is_part(m)) r = m;
else l = m + 1;
}
return x;
}