这道题要二分答案,怎么二分呢?
题目要把一个连续的序列分成k部分,当k一定时,要求值和最大部分的值尽量小,如果有多解,就在这个基础上,第一部分的和尽量小,如果仍有多解,就第二部分的和尽量小,以此类推;
所以题目就变成了找到一个最小的x,使得每一部分的和都<= x; 那么这个x的最大值是什么?假设有n个元素,很显然,在k = 0时取得,是这个序列的和,x的最小值是什么?在k = n-1取得,x = 最大值元素;所以现在我们要二分这个x;如果这个x能把这个序列分成大于k个连续子序列的话,就证明这个x偏小,那么就二分(x,R),如果这个x最多只能把这个序列分成k段的话,那么这个x偏大,所以二分(L,x);
循环的条件是L< R; 二分之后怎么判断这个x是否正确呢?要检查能不能把这个序列分成k段;每分一段的条件是什么?就是当这一段的和大于x了,所以就要分段了,这样进一个循环(i = n-1; i >= 0; i–)之后,用一个num来统计已经分的段数,如果num-1 > k(因为分一段会产生两段,分两段会产生三段,所以是num-1), 就证明这个x偏小,就返回false;否则,返回true;判断的条件是sum+A[i]> x;如果符合就分段;否则sum+=A[i];
找到最小的x后就根据这个x来输出,如果sum+A[i]> x|| i+1 < remain(剩余段数+1),就用一个数组标记这个位置,位置是i+1(i也可以,但输出要改);然后就输出就ok了
#include<iostream>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int maxn = 510;
ll A[maxn], B[maxn];
int n, k;
bool judge(int mid)
{
int num = 1;
ll sum = 0;
for(int i = 0; i < n; i++)
{
if(sum + A[i] <= mid) sum += A[i];
else{
sum = A[i];
num++;
if(num > k) return false;
}
}
return true;
}
void print(ll R)
{
ll sum = 0;
int num = k;
memset(B,0,sizeof(B));
for(int i = n-1; i >= 0; i--)
{
if(sum + A[i] > R || i+1 < num)
{
sum = A[i];
B[i+1] = 1;
num--;
}
else sum += A[i];
}
printf("%d",A[0]);
for(int i = 1; i < n; i++)
{
if(B[i]) printf(" /");
printf(" %d",A[i]);
}
printf("\n");
}
int main()
{
int T;
scanf("%d",&T);
ll L, R;
while(T--)
{
L = R= 0;
scanf("%d%d",&n,&k);
for(int i = 0; i < n; i++)
{
scanf("%lld",&A[i]);
R += A[i];
L = max(L,A[i]);
}
//L--;
while(L+1 < R)
{
ll mid = L + (R-L)/2;
if(judge(mid)) R = mid;
else L = mid;
}
print(R);
}
return 0;
}