题目描述
把一个包含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,不是最小。
解法:贪心+分治(C++)
我们将算法分为两个部分:
(1)判断命题
设对于某一数 x x x,是否存在 m m m个连续子序列使得所有的 S ( i ) ⩽ x S(i)\leqslant x S(i)⩽x,如果命题成立最小的 x x x即为最终所求答案。
对于该命题的判断,只需要利用贪心算法从左到右搜索即可。
(2)二分猜数
因为我们也不知道最小的 x x x为多少,所以我们利用二分的方式来查找 x x x
最终,算法的时间复杂度就是 O ( N l o g S ) O(NlogS) O(NlogS),其中 S = ∑ i n u m s [ i ] S=\sum_i nums[i] S=∑inums[i]
#include <bits/stdc++.h>
using namespace std;
int n, m;
vector<int> nums(1000, 0);
// 判断命题是否成立
bool helper(int x)
{
int s = 0, t = 0;
for(int i=0;i<n;i++)
{
if(nums[i]>x) return false;
if(s+nums[i]>x)
{
s = nums[i];
t++;
if(t>m-1) return false; // t=m-1时,说明存在m-1个划分,m个子序列
}
else s += nums[i];
}
return true;
}
// 二分猜数
int Solution(vector<int>& nums)
{
int left = 0, right = 0;
for(int i=0;i<n;i++)
right += nums[i];
while(left<right)
{
int mid = left+(right-left)/2;
if(helper(mid)) right = mid;
else left = mid+1;
}
return left;
}
int main()
{
cin >> n >> m;
for(int i=0;i<n;i++) cin >> nums[i];
int ans = Solution(nums);
cout << ans << endl;
return 0;
}