AcWing 102. 最佳牛围栏(实数二分)

题目链接:点击这里

在这里插入图片描述
在这里插入图片描述
题意:给定一个正整数数列 A A ,求一个平均数最大的、长度不小于 L L 的子段。

二分答案,判定“是否存在一个长度不小于 L L 的子段,平均数不小于二分的值”。

平均数的处理技巧:

如果把数列中每个数都减去二分的值,就转化为判定“是否存在一个长度不小于 L L 的子段,子段和非负”。

子段和可以转化为前缀和相减的形式,即设 s u m i sum_i 表示 A 1 A i A_1 \sim A_i 的和,则有:

仔细观察上面的式子可以发现,随着 i i 的增长, j j 的取值范围 0 i L 0 \sim i-L 每次只会增大 1 1 。换言之,每次只会有一个新的取值进入 m i n { s u m j } min\left\{sum_j\right\} 的候选集合,所以我们没有必要每次循环枚举 j j ,只需要用一个变量记录当前最小值,每次与新的取值 s u m i L sum_{i-L} 取min就可以了。

#include<iostream>
#include<algorithm>
#include<string>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<climits>

using namespace std;
typedef long long ll;
const int MOD = 10000007;
const int INF = 0x3f3f3f3f;
const double PI = acos(-1.0);
const double eps = 1e-5;
const int maxn = 100010;

int N, F;
double a[maxn], b[maxn], sum[maxn];

bool check(double ave)
{
	for(int i = 1; i <= N; ++i)
		b[i] = a[i] - ave;
	
	for(int i = 1; i <= N; ++i)
		sum[i] = sum[i-1] + b[i];
	
	double ans = -1e10;
	double minn = 1e10;
	for(int i = F; i <= N; ++i)
	{
		minn = min(minn, sum[i-F]);
		ans = max(ans, sum[i]-minn);
	}
	
	if(ans>0)	return true;
	else	return false;
}

int main()
{
    scanf("%d%d", &N, &F);
    
    for(int i = 1; i <= N; ++i)
    	scanf("%lf", &a[i]);
    
    double left = 0, right = 2000;
    while(right-left>eps)
    {
    	double mid = (left + right) / 2;
    	if(check(mid))	left = mid;
    	else	right = mid;
	}
    
    printf("%d\n", int(right*1000)); 
    return 0;
}
发布了727 篇原创文章 · 获赞 111 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/qq_42815188/article/details/104266028