题目
按顺序给你N个数,将这N个数分成连续的M段,使得这M段每段的和中的最大值最小,输出最小值(1<=N<=100000,1<=M<=N,每个数在1到10000之间),如果有多种可能的话,尽量在前面进行划分。
思路
1.最大值最小:好像是一种很常见的优化目标。。。我看人家别人blog都把这道题加上这个了,我也加上好了。
2.优化问题 转 判定问题+二分:(二分运用典例)
这才是这道题的重点。
本题的优化问题是:找最小的S,S为使整个序列分成k份,每份的和都能小于的值。
转化为判定问题+二分:设P(x)返回x是否能满足上述S的条件。如果P(x)为false,最优解应比x小;如果P(x)为true,最优解应比x大。从而可以用二分查找。
可以转换的条件:解的存在具有单调性。即x1处为解,x2处非解,则最优解一定在[x1,x2),或换过x1x2来。
(qwq神殿群,ks告诉我的)
3.正确的二分写法应为前闭后开,不然写起来各种个问题,详见代码注释。
4.我看出来了,你这道题的输出是存心想难为我胖虎。所以我就把LRJ的print()函数搬过来了(捂脸)。
代码
#include <cstdio>
#include <cstdlib>
#include <cstring>
#define ll long long
const int maxn = 500 + 100;
int n, k, A[maxn], biaoji[maxn];
bool P(int x) {
int last = x, cnt = 0;
for (int i = 0; i < n; i++) {
if (A[i] > x) return false;
if (A[i] <= last) {
last -= A[i];
}
else {
if (cnt == k - 1) return false;
cnt++;
last = x-A[i];
}
}
return true;
}
// print()函数取自LRJ aoapc2 github例题代码
int last[maxn];
void print(long long ans) {
long long done = 0;
memset(last, 0, sizeof(last));
int remain = k;
for (int i = n - 1; i >= 0; i--) {
if (done + A[i] > ans || i + 1 < remain) {
last[i] = 1; remain--; done = A[i];
}
else {
done += A[i];
}
}
for (int i = 0; i < n - 1; i++) {
printf("%d ", A[i]);
if (last[i]) printf("/ ");
}
printf("%d\n", A[n - 1]);
}
int main() {
//freopen("output.txt", "w", stdout);
int T;
scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &k);
ll sum = 0;
for (int i = 0; i<n; i++){
scanf("%d", &A[i]);
sum += A[i];
}
/*
//错误的二分:
看起来正确,实际会在L=199,R=201时,若P(200)=true,则会输出答案199
ll L = 0, R = sum;
while (L < R) {
ll M = L + (R - L) / 2;
if (P(M)) R = M-1;
else L = M + 1;
}
*/
ll L = 0, R = sum+1; // 二分的L,R应该为前闭后开区间
while (L < R) {
ll M = L + (R - L) / 2;
if (P(M)) R = M;
else L = M + 1;
}
print(L);
}
return 0;
}