/*
* 算法竞赛入门经典 8.3.5 最大值最小化
* 问题描述:把一个包含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,不是最小。
* 问题分析:能否使m个连续子序列所有的s(i)均不超过x,则该命题成立的最小的x即为答案。该命题不难判断,只需贪心,每次尽量从左
* 向右尽量多划分元素即可。我们把该问题转化为递归分治问题,类似于二分查找。首先取Sum和元素最大值的中值x,如果命题为假,那么答案比x大;
* 如果命题为真,则答案小于等于x。问题得解,复杂度为O(n*logSum)
* */
import java.util.Scanner;
public class MaxToMin {
int[] arr;
int sum;
int max;
int m;
int n;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
MaxToMin mm = new MaxToMin();
while((mm.n = scanner.nextInt()) > 0) {
mm.arr = new int[mm.n];
mm.m = scanner.nextInt();
mm.sum = 0;
mm.max = 0;
for(int i=0; i<mm.n; i++) {
mm.arr[i] = scanner.nextInt();
mm.sum += mm.arr[i];
if(mm.max < mm.arr[i]) mm.max = mm.arr[i];
}
System.out.println(mm.divide());
}
}
private boolean isDivide(int x) {//能否将序列划分成m个各子序列和不大于x的序列
boolean ok = true;
int s=0;// 当前划分段的和
int count = 0;//计数划分线 不能大于m-1;
for(int i=0; i<n; i++) {
if(arr[i] > x) {//某个元素值超过x 不能划分 退出
ok = false;
break;
}
if(s + arr[i] > x) {//当前元素不能加到该段了
count ++;//增加一段
s = arr[i];//下一段和
if(count > m-1) {//划分段数过多
ok = false;
break;
}
}else {
s += arr[i];
}
}
return ok;
}
private int divide() {
int x = max, y = sum;
while(x < y) {
int mid = x + (y-x)/2;
if(isDivide(mid)) {
y = mid;
} else {
x = mid+1;
}
}
return x;
}
}
最大值最小化
最新推荐文章于 2023-03-03 10:20:45 发布