描述:
农夫约翰是一个精明的会计师。他意识到自己可能没有足够的钱来维持农场的运转了。他计算出并记录下了接下来 N (1 ≤ N ≤ 100,000) 天里每天需要的开销。
约翰打算为连续的M (1 ≤ M ≤ N) 个财政周期创建预算案,他把一个财政周期命名为fajo月。每个fajo月包含一天或连续的多天,每天被恰好包含在一个fajo月里。
约翰的目标是合理安排每个fajo月包含的天数,使得开销最多的fajo月的开销尽可能少。
输入:
第一行包含两个整数N,M,用单个空格隔开。
接下来N行,每行包含一个1到10000之间的整数,按顺序给出接下来N天里每天的开销。
输出:
一个整数,即最大月度开销的最小值。
样例输入:
7 5
100
400
300
100
500
101
400
样例输出:
500
最大值最小化问题不易直接求解,可尝试简化问题,将求最大值最小化问题
转化成判定性问题:判断最大开销为x时是否能分成至多m个组,为什么说至多,
因为当x过小导致分组大于m时,不符题意,而x分组个数小于等于m时,是一定
包含最优解的,解释如下:
那么目标转换成从小到大枚举x直到找到第一个可行的x即是最优解,为了加快算法速度
可使用二分查找代替顺序枚举,因此问题转换成判定性问题和二分查找。详细见代码注释
直接上代码(Java)
import java.io.*;
public class 最大值最小化 {
static StreamTokenizer s=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
static PrintWriter out=new PrintWriter(System.out);
//min表示枚举区间的左边界,因为分组至少单个为一组,所以min就是单个开销的最大值
static int n,m,min;
static int arr[]=new int[100010];
public static void main(String[]args)throws IOException
{
n=nextint();
m=nextint();
int sum=0;
for(int i=0;i<n;i++)
{
arr[i]=nextint();
sum+=arr[i];
min=Math.max(min, arr[i]);
}
int res=bsearch(sum,m);
System.out.println(res);
//最大值最小化问题,
//思路:贪心+二分(在[min,sum]区间中二分,拿到的值t(假设当前值为最优解即最几个分组的最大值)进行判断:
//遍历数组,如果累加值小于t就继续遍历直到大于t,cnt++,
}
//二分
public static int bsearch(int sum,int m)
{
int l=min,r=sum;
while(l<r)
{
int mid=(l+r)/2;
if(judge(mid,m))
{
r=mid;//mid暂时是最优解,所以要包括在内
}
else {
l=mid+1;
}
}
return r;
}
public static boolean judge(int x,int m)
{
//记录组的数量;
int cnt=0;
//temp保留每次的累加值
int temp=0;
for(int i=0;i<n;i++)
{
//分组个数大于等于m必定无最优解
//(等于也算因为cnt已经等于目标分组数,还没分完就超了),小于m才可能有最优解
if(cnt>=m)return false;
if(temp+arr[i]<=x)
{
temp+=arr[i];
}
else
{
//temp清零并预先把下个分组的初始值赋给他
temp=arr[i];
cnt++;
}
}
//运行到这里说明cnt<=m,x暂时考虑为最优解,返回TRUE
return true;
}
//快读
public static int nextint()throws IOException{
s.nextToken();
return (int)s.nval;
}
}