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

题目链接:点击这里

AcWing 102. 最佳牛围栏(实数二分)_第1张图片
AcWing 102. 最佳牛围栏(实数二分)_第2张图片
题意:给定一个正整数数列 A A A,求一个平均数最大的、长度不小于 L L L 的子段。

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

平均数的处理技巧:

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

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

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

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

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;
}

你可能感兴趣的:(二分)