题目描述
今天小田田去看了电影《姜子牙》,回来的路上在思考一个问题,如果姜子牙想去幽都城总共要经过n个城池,而他要分为m次按顺序通过这n个城池,这m次前进每次消耗的体力为该次通过城池所消耗体力之和,现在要使姜子牙这m次前进中消耗体力的最大值最小,那么这个最大值将是多少。
输入
第一行两个正整数n,m
第二行为通过这n个城池消耗的体力
输出
m次前进中消耗体力最大值的最小值
输入样例
5 3
4 2 4 5 1
输出样例
6
数据范围
m<=n<=10^5
所有城池消耗体力之和小于10^9
思路分析
n个数分成m组,求这些组组和最大值的最小值
即
①对一个值ans,使得序列可以被分成连续的m段,且每一段的和不大于ans。
判断某一个值ans是否满足上述条件的方法:
定义变量 s,作为当前扩展子段的起始点,初始 s = 0。
从 s 出发不断向右扫描,直到 t 位置,满足 sum( a[s…t] ) > max_value, 则将 s[ s, t-1 ] 作为一个子段。
令 s = t ,回到步骤(2),直到扫描整个序列。
如果最后求得的组数比m大,则ans不满足
反之满足
②对于一个满足条件的ans,则比ans大的数都满足条件,所以我们要取所有满足条件的ans中的最小值
由于答案是离散的,我们可以采用二分逼近的方法求得最终答案。
二分的范围是【数组中元素最大值,数组中所有元素的和】。
把每一个mid=(low+high)/2;作为上述ans判断
- 如果满足上述条件,说明【mid,high】全部满足条件,此时应检查有无比mid更小的满足条件的值,即所求值在【low,mid】之间
- 如果不满足上述条件,说明所求的值在【mid+1,high】之间
AC代码
#include<stdio.h>
int a[100010];
int value(int a[],int n,int m);
int main()
{
int i,n,m;
scanf("%d%d",&n,&m);
for(i=0;i<n;i++)
scanf("%d",&a[i]);
printf("%d\n",value(a,n,m));
}
int value(int a[],int n,int m)
{
int low=a[0],high=0;
int i;
for(i=0;i<n;i++)
{
high+=a[i];
if(low<a[i]) low=a[i];
}
while(low<high)
{
int mid=(low+high)/2;
int sum=0;
int cnt=1;
for(i=0;i<n;i++)
{
sum+=a[i];
if(sum>mid)
{
sum=a[i];
cnt++;
}
}
if(cnt>m)
low=mid+1;
else
high=mid;
}
return low;
}
拓展
此类问题经典题面:
一个int型数组,数组元素代表每件事工人工作时长,现有m个工人,这些工人可以同时工作,问最短多久工作可以完成