UVa 714 Copying books 贪心+二分 最大值最小化

时间:2023-03-10 07:00:45
UVa 714 Copying books 贪心+二分 最大值最小化

题目大意:

要抄N本书,编号为1,2,3...N, 每本书有1<=x<=10000000页, 把这些书分配给K个抄写员,要求分配给某个抄写员的那些书的编号必须是连续的。每个抄写员的速度是相同的,求所有书抄完所用的最少时间的分配方案。

题目中的要求是去求划分的子序列的最大值尽量小,最大值最小化,如果从划分的角度看,无法获得好的思路,我们可以从值得角度考虑,所要求的最小的最大值必定是从[amax,sum(总和)]中取得的,那么我们可以二分法的方式猜测一个数字,看它是否满足要求,如果满足要求,我们可以继续缩小范围。

实现的另一个关键是划分,题目说如果有多解的话,前面的要求尽量小,那么我们的划分必然是从右往左划分,我们可以先做判断,如果都要求划分的值尽量达到最大值,它的划分个数小于要求的划分数,它就是满足条件的,因为我们可以将某一个划分组的序列拆解下来(子序列在拆解),它也一定是满足条件的,因为小于最大值肯定是对的,在贪心的过程中,一旦还需要划分组数正好等还剩下的整数数量的话,直接将每一个数作为一个划分组即可。

下面一篇博文将重点介绍一下二分查找的有关细节。

 #include<cstdio>
#include<cstring>
#define MAXN 505
using namespace std;
int num[MAXN];
int mark[MAXN];
int n,m,k;
long long low=-,high=;
void init(){
low = -;
high = ;
memset(mark,,sizeof(mark));
}
bool solve(long long mid){
//进行判断是否可以划分为某个最小最大值的序列
long long sum=;
int t=;
for(int i = m-;i >= ; i--){
if(sum + num[i] > mid){
sum = num[i];
t++;
if(t > k)
return false;
}
else
sum += num[i];
}
return true;
}
void print(long long s){
long long sum = ;
int t = ,i,j;
for(i = m-;i >= ; i--){
if(sum + num[i] > s){
//贪心的关键,尽量值靠近最大值
sum = num[i];
mark[i] = ;
t++;
}
else{
sum +=num[i];
}
if(k - t == i + ){
//贪心的关键
//如果剩下来的值的数量正好等于要划分的组数那么每一个数为一组
for(j = ;j <= i; j++){
mark[j] = ;
}
break;
}
}
for(i = ;i < m- ; i++){
printf("%d ",num[i]);
if(mark[i]==){
printf("/ ");
}
}
printf("%d\n",num[m-]);
}
int main(){
long long left,right,mid;
scanf("%d",&n);
while(n--){
init();
scanf("%d%d",&m,&k);
for(int i = ; i < m ;i++){
scanf("%d",&num[i]);
if(low < num[i])
low = num[i];
high+=num[i];
}
left = low;
right = high;
while(left <= right){
memset(mark,,sizeof(mark));
mid = left + (right - left)/;
if(solve(mid)){
right = mid - ;
}
else
{
left = mid + ;
}
}
print(left);
}
return ;
}