[好题][二分][前缀和]Best Cow Fences POJ2018

23 篇文章 0 订阅

Farmer John's farm consists of a long row of N (1 <= N <= 100,000)fields. Each field contains a certain number of cows, 1 <= ncows <= 2000.

FJ wants to build a fence around a contiguous group of these fields in order to maximize the average number of cows per field within that block. The block must contain at least F (1 <= F <= N) fields, where F given as input.

Calculate the fence placement that maximizes the average, given the constraint.

Input

* Line 1: Two space-separated integers, N and F.

* Lines 2..N+1: Each line contains a single integer, the number of cows in a field. Line 2 gives the number of cows in field 1,line 3 gives the number in field 2, and so on.

Output

* Line 1: A single integer that is 1000 times the maximal average.Do not perform rounding, just print the integer that is 1000*ncows/nfields.

Sample Input

10 6
6 
4
2
10
3
8
5
9
4
1

Sample Output

6500

题意: 给出n个数,要求寻找一段长度不小于k的区间,并使其平均值最大,将最大平均值*1000再取整输出。

分析: 比较容易想到要二分答案,那么check(x)函数需要判断是否存在一段长度不小于k的区间且其平均值大于等于x,而且要在O(n)的复杂度以内。由于答案是输出整数,这里我们用整数上的二分就可以了,因为a[i]都在[1, 2000]范围内,平均值*1000应该在[1000, 2000000]范围内,所以要在1000到2000000内二分,但如果你这么写一定会wa一天(我就是),因为这题题目有问题,存在a[i]为0的数据!也就是要在0到2000000范围内二分。避开这个坑点后考虑如何设计check函数,我们假设区间左端点为l,右端点为r,check中就需要我们判断是否存在[l, r]使得(s[r] - s[l-1]) / (r-l+1) >= x,变个形也就是s[r] - s[l-1] - x*(r-l+1) >= 0,左边这个值其实就是[l, r]内每个值都减去x然后再求和,也就是对原数组a[i]处理一下得到一个新的t数组,t[i] = a[i] - x,这样有什么好处呢?此时我们只需要判断是否存在一段长度不小于k的区间且其和 >= 0,这个问题其实就是求一下连续区间和的最大值,若最大值 >= 0那么return true,反之return false。至于连续区间和的最大值就用前缀和求就行了,最暴力的做法是外层for循环从[k, n]枚举区间右端点r,然后内层for循环从[1, r-k+1]枚举左端点l,不过这样显然会超时,其实内层for循环是可以被优化掉的,如果我们知道前缀和数组s[0]~s[i]的最小值(很容易预处理出来),那么直接s[r]减去s[0]~s[r-k]的最小值就是以r为右端点的长度不小于k的所有区间和的最大值,这个过程是O(1)的,因此check的总时间复杂度为O(n)。

具体代码如下:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <string>
#define int long long
using namespace std;

int n, k, a[100005], t[100005], s[100005];

bool check(int x)//判断是否存在一段长度大于等于k的且平均值大于等于x的区间 
{
	for(int i = 1; i <= n; i++)
	{
		t[i] = a[i]*1000-x;
		s[i] = s[i-1]+t[i]; 
	}
	int ans = -0x3f3f3f3f3f3f3f3f;//ans保存长度大于等于k的区间和的最大值 
	int _min = 0;//_min保存s[0]~s[i-k]的最小值 
	for(int i = k; i <= n; i++)//枚举区间右端点,左端点取[1,i-k+1] 
	{
		_min = min(_min, s[i-k]); 
		ans = max(ans, s[i]-_min);
		if(ans >= 0)
			return true; 
	}
	return false; 
}

signed main()
{
	cin >> n >> k;
	for(int i = 1; i <= n; i++)
		scanf("%lld", &a[i]);
	int l = 0, r = 2000000, ans = -1;
	while(l <= r)
	{
		int m = l+r>>1;
		if(check(m))
		{
			ans = m;
			l = m+1;
		}
		else
			r = m-1;
	}	
	cout << ans;
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值