《算法竞赛进阶指南》打卡-基本算法-AcWing 102. 最佳牛围栏:浮点数二分、前缀和、双指针
题目
农夫约翰的农场由 N 块田地组成,每块地里都有一定数量的牛,其数量不会少于 1 头,也不会超过 2000 头。
约翰希望用围栏将一部分连续的田地围起来,并使得围起来的区域内每块地包含的牛的数量的平均值达到最大。
围起区域内至少需要包含 F 块地,其中 F 会在输入中给出。
在给定条件下,计算围起区域内每块地包含的牛的数量的平均值可能的最大值是多少。
输入格式
第一行输入整数 N 和 F,数据间用空格隔开。
接下来 N 行,每行输入一个整数,第 i+1 行输入的整数代表第 i 片区域内包含的牛的数目。
输出格式
输出一个整数,表示平均值的最大值乘以 1000 再 向下取整 之后得到的结果。
数据范围
1≤N≤100000
1≤F≤N
输入样例:
10 6
6
4
2
10
3
8
5
9
4
1
输出样例:
6500
题解
来源:最佳牛围栏
题目分析
选取长度最短为f的子序列,其平均值最大。这里的难度在于长度可以大于f,而不是固定f。
我们的思路是,平均值最大问题(最优化问题)转化为一个判定问题:猜想(二分)出来一个平均值mid,判断是否存在一个长度大于等于f的连续子序列满足。
有关平均值的常用技巧:一个序列的平均值大于mid ⟺ \Longleftrightarrow ⟺把序列中每个元素都减去mid,然后求和,只要和大于0
使用双指针:i 和 j,其间隔为f。需要记录i指针前面部分的最小值,每次判断sum[j]大于等于i前面区间的最小值:sum[j] ≥ min[sum(0 ~ i)],如果满足,则二分往后半区间移动。
注意,这里就满足了区间长度大于等于m:因为i 和 j已经间隔m,然后如果取i前面的值,则区间长度大于m。
在代码中:minv是最小的前缀和,sum[j]也是前缀和,只要sum[j]减去minv大于零,表示从前缀和是minv的位置到j这个区间中的所有值的和大于0,也就是平均值大于mid。
时间复杂度: O ( n l o g r ) O(nlogr) O(nlogr),其中r为二分的长度,这里是≤ 2000,n为序列的长度。
ac代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int cows[N];
int n, m;
double sum[N];
bool check(double avg) {
// 每个数变成它减去平均数,再求前缀和
for (int i = 1; i <= n; i ++) sum[i] = sum[i - 1] + cows[i] - avg;
double minv = 0;
// 双指针
for (int i = 0, j = m; j <= n; j ++, i ++) {
minv = min(minv, sum[i]); // [0, i]的最小值
if (sum[j] >= minv) return true;
// sum[j] >= minv:存在长度≥m,且平均值≥ avg
}
return false;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i ++) cin >> cows[i];
double l = 0, r = 2000; // 可取的平均值一定在[0, 2000]之间
// 二分平均值
while (r - l > 1e-5) {
double mid = (l + r) / 2;
if (check(mid)) l = mid; // 如果mid满足啦,就判断更大的平均值,即l = mid
else r = mid;
}
cout << int(r * 1000) << endl;
}
更多推荐
所有评论(0)