原题连接ios
给定一段长度为1e5的序列A,而且给咱们一个范围 \([S, T]\), 要求咱们求出一段长度在这个范围内的连续子序列,而且要使这个连续子序列的平均值最大,输出这个平均值。数组
一开始想的是连续子段和相关,因而很久都没有想到正解。
spa
这个题首先能够肯定可以使用二分答案的方法,由于“符合条件的最大平均值”是单调的。
code
以后考虑如何检查序列中符合条件的子序列的平均值是否能达到给定的k。
队列
看上去咱们只须要先求前缀和,而后对每一个R,计算 \(max_{1 \le i \le R}A[L...R]\) 而后比较,可是问题在于咱们保证了左边最大的同时,右边的(R - L + 1) 却有可能很大而使得可行解不在 \(max_{1 \le i \le R}A[L...R]\) 处取到
get
因此再变换一次,得string
这样咱们每次检验时,先把A所有元素-k获得A‘数组,(前缀和数组也同时更新),而后对每一个R,计算 \(max_{1 \le i \le R}A’[L...R]\) ,此时的最大值就是 \(max_{1 \le i \le R}[A[L...R] - k * (R - L + 1)]\), 而后与0比较,就能够正确寻找可行解。
it
求最值的过程,因为子区间长度范围为\([S, T]\),因此咱们求最值的范围事实上是 $ max_{R - t \le i \le R - s}{sum_R - sum_i} $,也就是须要求长度为 \(t - s + 1\) 的区间上的 \(sum_i\) 的最小值,固定区间长度的最值问题,使用单调队列。io
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const double ff = 1e-4; double su[100005]; int n, s, t, m; struct ab { int l; double v; } que[100005]; bool chk(double k) { for (int i = 1; i <= n; ++i) { su[i] -= i * k; } su[0] = 0; bool fl = false; int hd = 1, tl = 1; for (int i = 0; i < m; ++i) { while (tl > hd && que[tl - 1].v >= su[i]) { --tl; } que[tl].l = i; que[tl++].v = su[i]; if (su[i + s] - que[hd].v >= 0) { fl = true; break; } } if (!fl) { for (int i = m; i <= n - s; ++i) { while (hd < tl && que[hd].l + m <= i) { ++hd; } while (tl > hd && que[tl - 1].v >= su[i]) { --tl; } que[tl].l = i; que[tl++].v = su[i]; if (su[i + s] - que[hd].v >= 0) { fl = true; break; } } } for (int i = 1; i <= n; ++i) { su[i] += i * k; } return fl; } int main() { scanf("%d", &n); scanf("%d%d", &s, &t); m = t - s + 1; su[0] = 0; double ll = 0, rr = 0; for (int i = 1; i <= n; ++i) { double xx; scanf("%lf", &xx); su[i] = su[i - 1] + xx; ll = min(ll, xx); rr = max(rr, xx); } while (ll + ff * 5 <= rr) { double mid = (ll + rr) / 2; if (chk(mid)) { ll = mid; } else { rr = mid; } } while (chk(ll)) { ll += ff; } printf("%.3f", ll - ff); return 0; }