【算法设计与数据结构】二分法解决最大值最小化问题——入门篇

时间:2022-08-07 19:03:25

问题描述

把一个包含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;
}