奥赛一本通 1434 Best Cow Fences
1434 Best Cow Fences
题目大意
给定一个整数序列 $\{a_n\}$,求长度不小于 $L$ 的子段的最大平均值(一本通题面描述为子序列有误,补充数据范围 $0 \le a_i \le 2000$)。
知识要点
二分、前缀和、斜率、单调队列
解题思路 1
二分枚举平均值 $v$,那么需要找到平均值不小于 $v$ 的子段。如果令 $b_i = a_i - v$,相当于在序列 $\{b_n\}$ 中找总和不小于零的子段,计算它的前缀和 $\{s_n\}$,即要求 $s_r - s_l \ge 0$。枚举区间右端点 $r$, 找到 $0 \le l \le r-L$ 范围内 $s_l$ 的最小值即可。
参考代码 1
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
int n, L, a[N];
long long s[N];
bool check(int v) {
for(int i = 1; i <= n; i++) s[i] = s[i - 1] + a[i] - v;
long long sl = 0; //维护前缀最小值
for(int r = L; r <= n;r++) {
sl = min(sl, s[r - L]);
if(s[r] >= sl) return 1;
}
return 0;
}
int main() {
scanf("%d%d", &n, &L);
for(int i = 1; i <= n; i++)
scanf("%d", &a[i]), a[i] *= 1000; //直接放大 1000 倍避免浮点数
int l = 0, r = 2e6;
while(l < r) {
int v = (l + r + 1) / 2;
if(check(v)) l = v;
else r = v - 1;
}
printf("%d\n", l);
return 0;
}
解题思路 2
显然前缀和序列 $\{s_n\}$ 是单调递增的,而平均值可以写作斜率的形式,考虑使用单调队列求解斜率的最大值。对于枚举的每个右端点 $r$,将对应范围内的左端点保留在队列中,关键在于如何找到斜率的最大值。
对于从左到右的三个点 $A, B, C$,如果斜率递减的话,那么在计算 $C$ 右边某个点产生的斜率最大值时就不可能选择 $B$,也就是单调队列中只需要保留让斜率递增的点,相当于构成了一个下凸壳。
假如某时刻单调队列中包含三个点 $A, B, C$,计算 $D$ 对应的斜率最大值时选择了 $C$,计算 $E$ 对应的斜率最大值时选择了 $B$。如果要确定单调队列中哪个点才是当前需要的斜率最大值,可以使用二分法。不过根据凸壳的性质可知,最优点在左侧的斜率肯定小于最优点在右侧的斜率,从得到全局最优的角度来看,单调队列中只需要保留当前最优点及其右侧的点,这样时间复杂度优化为 $O(n)$。
参考代码 2
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
int Q[N], l = 1, r = 0; //单调队列
long long ans, s[N];
long long slope(int i, int j) {
return (s[i] - s[j]) / (i - j);
}
int main() {
int n, L, a;
scanf("%d%d", &n, &L);
for(int i = 1; i <= n; i++)
scanf("%d", &a), s[i] = s[i - 1] + a * 1000;
for(int i = L; i <= n; i++) {
while(l < r && slope(Q[r - 1], Q[r]) >= slope(Q[r], i - L)) r--;
Q[++r] = i - L; //新元素入队
while(l < r && slope(Q[l], i) <= slope(Q[l + 1], i)) l++; //暴力找最值
ans = max(ans, slope(Q[l], i));
}
printf("%d\n", ans);
return 0;
}

浙公网安备 33010602011771号