题意:有一个序列,由m个整数组成,把这个序列划分为k个连续的子序列,使得每个子序列和的最大值最小,要求划分的子序列的和是递增的,问如何划分。
关于最大值的最小化问题,我们可以枚举子序列和的最大值,用二分的方法不断把最大值缩小,直到最优解,二分很容易,麻烦的就是我们如何划分,以及如何输出结果。
int judge(ll x) { //判断子序列和的最大值是否可以划分
ll t = 0, sum = 0; //t代表划分的次数, sum代表每个子序列的和
for(int i = 1; i <= m; i++) {
if(sum + num[i] > x) { //如果比最大值大,就代表依次划分
t++;
sum = num[i]; //将sum重新赋值
}
else sum += num[i]; //否则就把sum变大
if(i == m) t++; //这是最后一个子序列
if(t > k) return 0; //如果划分的总的子序列大于k,就不满足
}
return 1; //满足
}
输出问题主要就是要找到 反斜杠的位置,我们用一个数组p[]来表示,p[i] = 1,表示i这个位置有反斜杠,否则没有。为了满足子序列的和是递增的,我们从后往前枚举。
ll sum = 0;//每个子序列的和
for(int i = m+1; i >= 2; i--) {
if(sum + num[i-1] > mn && i+ans >= k) {//i+ans >= k表示需要划分,如果i=3,ans=0,k=4,那么一定不满足
ans++; //表示已经划分的区间
p[i] = 1; //表示此处有反斜杠
sum = num[i-1]; //重新赋值,为了方便起见我们i从m+1开始
}
else if(i+ans == k) { //当i+ans=k的时候,不需要划分了,因为每个数字就是一个子序列
ans++;
p[i] = 1; //表示此处有反斜杠
sum = num[i-1]; //重新赋值
}
else if(sum + num[i-1] <= mn) { //mn代表二分的结果,
sum += num[i-1];
}
}
最后的输出:
for(int i = 1; i <= m; i++) {
if(p[i]) printf("/ ");
printf("%lld", num[i]);
if(i != m) printf(" ");
}
printf("\n");
#include <stdio.h>
#include <string.h>
#define ll long long
ll m, k;
ll num[505];
int p[505];
int judge(ll x) {
ll t = 0, sum = 0;
for(int i = 1; i <= m; i++) {
if(sum + num[i] > x) {
t++;
sum = num[i];
}
else sum += num[i];
if(i == m) t++;
if(t > k) return 0;
}
return 1;
}
int main() {
int N;
scanf("%d", &N);
while(N--) {
memset(p, 0, sizeof(p));
scanf("%lld%lld", &m, &k);
ll mn;
ll l = 0, r = 0, mid, ans = 0;
for(int i = 1; i <= m; i++) {
scanf("%lld", &num[i]);
r += num[i];
if(num[i] > l) l = num[i];
}
//二分
while(l < r) {
mid = l-(l-r)/2;
if(judge(mid)) {
mn = mid;
r = mid;
}
else l = mid+1;
}
//找反斜杠的位置
ll sum = 0;
for(int i = m+1; i >= 2; i--) {
if((sum + num[i-1] > mn && i+ans >= k) || i+ans == k) {
ans++;
p[i] = 1;
sum = num[i-1];
}
else if(sum + num[i-1] <= mn) {
sum += num[i-1];
}
}
for(int i = 1; i <= m; i++) {
if(p[i]) printf("/ ");
printf("%lld", num[i]);
if(i != m) printf(" ");
}
printf("\n");
}
return 0;
}