问题:把一个包含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,不如刚才的好。
分析:给定序列后,我们可以知道任意划分的最大值的取值范围[0, sum(1...n)],一种较慢的方法是枚举每种取值情况,根据枚举值进行序列划分,若划分的组数大于m,说明没有满足条件的划分情况,需要增加枚举值;如果划分的组数小于m,说明找到了满足条件的划分情况。时间复杂度为O(n*sum);
思路扩展:既然找到了线性的枚举方法,我们不妨考虑下二分枚举的方法,考虑mid的情况是否满足条件,如何划分组数大于m,与线性枚举的情况相同;若划分的组数小于m,说明当前解满足条件,但可能还有比当前解还小的解,需进一步计算。
代码如下
#include <stdio.h>
#include <string.h>
int main(void)
{
int i;
int n, m;
int *arr;
while (scanf("%d%d", &n, &m) != EOF)
{
arr = new int[n];
int sum = 0;
for (i = 0; i < n; i++)
{
scanf("%d", &arr[i]);
sum += arr[i];
}
int left = arr[0];
int right = sum;
int mid;
while (left < right)
{
mid = (left + right) / 2;
int cnt = 1;
int subsum = 0;
for (i = 0; i < n; i++)
{
if (subsum + arr[i] > mid)
{
subsum = arr[i];
cnt++;
}
else
subsum += arr[i];
}
if (cnt > m)
left = mid + 1;
else
right = mid;
}
printf("%d\n", left);
int SubSum = 0;
for (i = 0; i < n; i++)
{
if (SubSum + arr[i] > left)
{
SubSum = arr[i];
printf("| ");
}
else
SubSum += arr[i];
printf("%d ", arr[i]);
}
delete [] arr;
}
return 0;
}
/*
6 3
1 2 3 2 5 4
*/