1434.【例题2】Best Cow Fences

信息学一本通 二分查找

【题目描述】

给定一个长度为n的正整数序列A。求一个平均数最大的,长度不小于L的子序列。

【输入】

第一行,n和L;

n个正整数,表示A。

【输出】

一个整数,表示答案的1000倍(不用四舍五入,直接输出)。

【输入样例】

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

【输出样例】

6500

【提示】

n ≤ 100000

【分析】

依题意,需要找到一个最大的平均值,此时有一段数量大于等于L的子序列存在。那么可以考虑采用二分查找的方式在平均值范围内尝试查找,这个范围可以估摸为0~1e6。

还需要知道前缀和的知识,后面一个前缀和 - 前面一个前缀和 = 这段子序列的和 。

题目要求子序列最少是L个,则i从L值开始,可以在前缀和sum[0]~sum[i-l]范围内找到一个最小值minn。

依次用前缀和的值 - minn,挑出最大的情况,最终maxx中值表示找到一段子序列和最大的。

即在当前平均值情况下,找到一段子序列和最大。因为前面计算前缀和时已经扣除了平均值,这个时候如果maxx >= 0 说明还可以再多扣一点平均值,平均值可以往大的方向调整,否则平均值需要往小的方向调整。

012345678910
序列a64210385941
扣除均值31-1705261-2
前缀和034310101517232422
minnmaxx

假设指定了一个平均值3,扣除3以后计算样例前缀和如上表,i从6开始查找,minn = 0, maxx = 24,表示1~9这个子序列扣除平均值后的和为24,说明还可以把平均值调大一点。

【完整代码】

#include <bits/stdc++.h>

double a[100005],sum[100005];
using namespace std;
int main(int argc, char *argv[]) {
	int n,l;
	cin >> n >> l;
	for(int i = 1; i <= n; i++){
		cin >> a[i];
	}
	//平均值范围内二分查找
	double left = 0, right = 1e6;
	while(right - left > 1e-6){
		//取中位数
		double mid = (left + right) / 2;
		//计算前缀和,计算时减去了平均值
		for(int i = 1; i <= n; i++){
			sum[i] = sum[i-1] + a[i] - mid;
		}
		//估摸着给一个初值
		double minn = 1e10, maxx=-1e10;
		/*题目要求子序列最少是L个,则则i从L值开始,可以在前缀和sum[0]~sum[i-l]范围内找到一个最小值minn。		
		依次用前缀和的值 - minn,挑出最大的情况,最终maxx中值表示找到一段子序列和最大的。
		即在当前平均值情况下,找到一段子序列和最大。因为前面计算前缀和时已经扣除了平均值,这个时候如果maxx >= 0 说明还可以再多扣一点平均值,平均值可以往大的方向调整,否则平均值需要往小的方向调整。
		*/
		for(int i = l; i <=n; i++){
			minn = min(minn, sum[i - l]);
			maxx = max(maxx, sum[i] - minn);
		}
		//根据maxx值调整平均值
		if(maxx >= 0){
			left = mid;
		}else{
			right = mid;
		}
	}
	//输出
	cout << int(right*1000);
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值