link
解题思路
第一步,把这破小数踢掉,全部乘1000
二分平均值mid,先把A序列全部减去mid,那么就是求一个长度不小于L的子段,且子段和不为0即可
怎么求这个子段呢?
首先想到前缀和, O ( n 2 ) O(n^2) O(n2),明显不行
求以y结尾的最大子段, m a x ( s u m [ y ] − s u m [ j ] ) ( 1 < = j < = y − L ) max(sum[y] - sum[j])(1<=j<=y-L) max(sum[y]−sum[j])(1<=j<=y−L)
y是顺序的,那么j的可能性变少
也就是说以y结尾时答案由 s u m [ m i n j ] sum[minj] sum[minj]构成,以y+1结尾时,答案由 m i n ( s u m [ m i n j ] , s u m [ y + 1 − L ] ) min(sum[minj],sum[y+1-L]) min(sum[minj],sum[y+1−L])构成
O ( n ) O(n) O(n)可行
Code
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const long long maxn = 9223372036854775807;
long long n, m, a[100100], l, r, mid, ans, minj, s[100100];
bool check(long long x) {
memset(s, 0, sizeof(s));
for (int i = 1; i <= n; i++)
s[i] = s[i - 1] + (a[i] - x)//求出前缀和(序列和减去mid)
long long minj = maxn, ans = -maxn;
for (int i = m; i <= n; i++) {
minj = min(minj, s[i - m]);//省去j的循环
ans = max(ans, s[i] - minj);
}
return (ans >= 0);//答案非0
}
int main() {
scanf("%lld%lld", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
a[i] *= 1000;//抹去小数
r += a[i];
}
while (l <= r) {
mid = (l + r) / 2;
if (check(mid)) {
l = mid + 1;
ans = mid;
}else r = mid - 1;
}
printf ("%lld", ans);
}