UVA 714 Copying Books

这道题要二分答案,怎么二分呢?
题目要把一个连续的序列分成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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值