前往:我自己搭建的博客
题目
题解
此题要用到二分法,但不是传统意义上的在单调序列中查找数值,而是二分答案转化为判定,比较抽象。首先,定义“如果能将数列分为m段,且每段长度小于等于sum,则称sum为合法值”,那么题中要求的就是最小的合法值。易证:越大的sum越容易合法,且存在分界值s,小于s的sum都不合法,大于等于s的sum都合法(s就是题目要求的值)。将合法值简称“1”,不合法值简称“0”。则对于从小到大的sum有如下序列:……0000011111……。若从小到大或从大到小一个个验证合法性,速度较慢,考虑二分。二分的左右指针为l和r,mid=(l+r)/2,它们都表示sum值。如果mid为合法值,则s在其左侧或是其本身,那么搜索范围可由[l,r]缩小为[l,mid];如果mid为非法值,则s在其右侧,范围由[l,r]缩小为[mid+1,r]。
下面讲如何进行合法性检测:贪心即可。从左往右分段,每一段尽量多,如果最后分的段数小于等于m,则合法。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
int m,n;
ll a[maxn];
inline bool check(ll sum) //判断每段长度<=sum是否可行
{
ll now=0,cut=1; //now表示当前段已有长度,cut表示当前是第几段(注意初始值是1,不是0)
for(int i=1;i<=n;i++)
{
if(a[i]>sum) return 0; //注意这种情况
if(now+a[i]>sum)
{
if(cut==m) return 0;
now=a[i];
cut++;
}
else now+=a[i];
}
return 1;
}
inline ll erfen()
{
ll l=0,r=0;
for(int i=1;i<=n;i++) r+=a[i];
while(l<r)
{
ll mid=(l+r)>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
return l;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
printf("%lld\n",erfen());
return 0;
}