题目链接:点击这里
题意:给定一个正整数数列
,求一个平均数最大的、长度不小于
的子段。
二分答案,判定“是否存在一个长度不小于 的子段,平均数不小于二分的值”。
平均数的处理技巧:
如果把数列中每个数都减去二分的值,就转化为判定“是否存在一个长度不小于 的子段,子段和非负”。
子段和可以转化为前缀和相减的形式,即设 表示 的和,则有:
仔细观察上面的式子可以发现,随着 的增长, 的取值范围 每次只会增大 。换言之,每次只会有一个新的取值进入 的候选集合,所以我们没有必要每次循环枚举 ,只需要用一个变量记录当前最小值,每次与新的取值 取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;
}