题意:将m个序列分成k个连续的子序列,将各个子序列求和,分的子序列的和的最大值尽量小。
思路:
(1)枚举子序列和的最大值,从m个序列中最大值~m个序列的和 之间的数二分枚举最大值,每次枚举的值看能划分的个数与k值比较,如果<=k 枚举值小缩小,否则增大,直到二分结束后,左值即为最大值的尽量小化。如果直接找到一次相等k的话不能不是尽量小化,所以继续进行二分!
(2)求划分个数:遍历一遍序列,连续累加数字,当加上下一位时大于当前枚举的最大值时就要划分了!
(3)打印划分:用一个标记数组,倒的遍历序列(因为要靠前的尽量小,所以先算后面的尽量达到最大值,剩下前面的数肯定少了就小了!)继续连续累加数字,当加上下一位大于最大值的话就将此位置标记1即为划分,并且将划分次数减少一次, 或者 当划分次数大于等于剩余数个数时必须划分了,因为有可能连续和还没有加到最大值,还一直加,此时剩余的个数不够满足k次划分了!
(4)输出原序列且当标记数组为1时输出"/"。
参考:紫书代码库
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long LL;
const int maxn = 500 + 5;
int s[maxn],mark[maxn],n,k;
inline int P(LL v){//最大值最小化为v时划分的次数
int cnt = 1;
LL sum = 0;
for(int i=0;i<n;i++){
if(sum + s[i] < v) sum += s[i];//向右划分
else{sum = s[i];cnt++;}//记录划分次数
}
return cnt;
}
inline int binarySearch(LL l,LL r){//二分查找最大值
while(l < r){
LL pos = l + (r - l)/2;
if(P(pos) <= k) r = pos;//当划分次数小于k时说明当前最大值大了,右值缩小
else l = pos + 1;//
}
return l;
}
inline void divide(int ans){//划分
memset(mark,0,sizeof(mark));
LL sum = 0;
int cnt = k;
for(int i=n-1;i>=0;i--)
if(sum + s[i] > ans || i+1 < cnt){//当和大于最大值 或 剩余的元素个数已经等于当前剩余的划分个数时必须划分,因为要求划分k次!
sum = s[i];//将求和归位,继续下一次的相加
mark[i] = 1;//在当前位置划分
cnt--;//划分次数减少
}
else sum += s[i];
for(int i=0;i<n-1;i++){
printf("%d ",s[i]);
if(mark[i]) printf("/ ");
}
printf("%d\n",s[n-1]);
}
int main()
{
int t;
scanf("%d",&t);
while(t--){
scanf("%d%d",&n,&k);
int maxp = -1;
LL sum = 0;
for(int i=0;i<n;i++) {
scanf("%d",&s[i]);
maxp = max(maxp,s[i]);//求最大值,用作二分查找最大值的左值
sum += s[i];//求和,用作二分查找最大值的右值
}
divide(binarySearch((LL)maxp,sum));
}
return 0;
}