【题目链接】
ybt 1436:数列分段II
洛谷 P1182 数列分段 Section II
【题目考点】
1. 二分答案
2. 贪心
【解题思路】
求最大值最小问题,一般可以使用二分解决。
原题是寻找一种能使得最大子段和最小的子段划分方案。正向思维是先进行子段划分,而后记录该划分下的最大子段和。穷举所有子段划分的情况,求最大子段和的最小值。但是子段划分的情况很多,如果枚举所有子段划分的方案,必然超时。
此处可以进行反向思考,上来就确定子段划分的最大子段和不能超过某一数值,而后看否存在满足这一要求的子段划分方案。通过二分算法,不断选取最大子段和不能超过的数值,最后就可以确定最大子段和的最小值。
二分算法可以将枚举所有方案问题转为判定一个方案是否可行的问题。
设二分答案的取值范围:
- 最小值l:由于序列中的数字都是正整数,那么所有数字的加和一定大于0,因此最小值l设为0。
- 最大值r:可以设为序列中所有数值的加和,也可以设一个一定大于等于所有数加和的很大的数字,该题说答案不超过 1 0 9 10^9 109,因此最大值r就可以设为 1 0 9 10^9 109
该题求的是不同的将序列划分为M个子段的方案中的最大子段和的最小的方案的最大子段和,这是求满足某一条件最小值的问题,可以使用相应的二分答案写法完成。
答案x需要满足的条件为:在最大子段和小于等于数值x的前提下,是否可能存在一种将序列划分为M个子段的划分方案。
最大子段和小于等于数值x,也就是所有子段的子段和都不能超过x。
如果序列中某个数大于x,这一个数构成的子段就已经大于x了,不满足上述要求,因此该情况不满足条件。
我们可以通过贪心算法求出:在所有子段的子段和都小于等于数值x的前提下,序列可以划分成的最少子段数,具体方法见信息学奥赛一本通 1428:数列分段 | 洛谷 P1181 数列分段 Section I。
- 如果划分成的最少子段数小于等于M,那么可以继续将任意子段继续划分为多个子段,直到子段数量等于M个(已知元素个数N大于等于M,因此这N个数一定可以分成M个子段)。因为每个数字都大于0,继续划分出的子段的加和只可能比原子段的加和更小,因此仍满足各子段的最大子段和小于等于x,此时答案x满足条件。
- 如果划分成的最少子段数大于M,则答案x不满足条件。
最后输出二分答案的结果。
【题解代码】
解法1:二分答案
- 写法1:
#include<bits/stdc++.h>
using namespace std;
int a[100005], n, m;
bool check(int x)//满足每个子段的和<=x
{
int sum = 0, ct = 1;
for(int i = 1; i <= n; ++i)
{
if(a[i] > x)
return false;
if(sum+a[i] <= x)
sum += a[i];
else
{
ct++;
sum = a[i];
}
}
return ct <= m;
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; ++i)
cin >> a[i];
int l = 0, r = 1e9;
while(l < r)
{
int mid = (l+r)/2;
if(check(mid))
r = mid;
else
l = mid+1;
}
cout << l;
return 0;
}
- 写法2:
#include<bits/stdc++.h>
using namespace std;
int a[100005], n, m, tsum;
bool check(int x)//满足每个子段的和<=x
{
int sum = 0, ct = 1;
for(int i = 1; i <= n; ++i)
{
if(a[i] > x)
return false;
if(sum+a[i] <= x)
sum += a[i];
else
{
ct++;
sum = a[i];
}
}
return ct <= m;
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; ++i)
{
cin >> a[i];
tsum += a[i];//tsum:所有数字的加和
}
int l = 0, r = tsum;
while(l <= r)
{
int mid = (l+r)/2;
if(check(mid))
r = mid-1;
else
l = mid+1;
}
cout << l;
return 0;
}