AcWing 102. 最佳牛围栏
1、问题
2、分析
(1)暴力做法
看到这道题以后,我们可以先想一个最暴力的做法,就是我们去枚举所有长度至少为 F F F的区间,然后求出这个区间的和,再求出这个区间的平均值。最后在这些平均值之间取一个最大值。
那么这个暴力做法的时间复杂度是多少呢?枚举所有符合长度要求的区间,该过程在最坏条件下的复杂度是 O ( n 2 ) O(n^2) O(n2),求出区间的和,复杂度是 O ( n ) O(n) O(n)。那么总的时间复杂度就是 O ( n 3 ) O(n^3) O(n3)。
很明显这个做法是超时的,那么对于这个暴力的做法,我们可以给出一个小小的优化,即我们通过前缀和算法将求区间的时间复杂度优化到 O ( 1 ) O(1) O(1),优化后,该做法的时间复杂度是 O ( n 2 ) O(n^2) O(n2)。优化后依旧是超时的。
(2)二分答案+前缀和+DP
受朴素做法的启发,我们先求一下前缀和,将前缀和数组记作: s i si si。
这道题我们可以换个思路想,假设给你一个平均值 x x x。我们要做的是判断这个平均值是否能够达到。
换句话说,我们就是要判断是否存在一个区间,使得这个区间和的平均值达到了 x x x。
将其转化为数学表达式即:
是否存在一个区间 i , j i,j i,j,其中 i − j + 1 > = F i-j+1>=F i−j+1>=F,满足:
s i − s j − 1 i − j + 1 ≥ x \frac{si - sj - 1}{i-j+1} \geq x i−j+1si−sj−1≥x
等价于
s i − s j − 1 ≥ x ∗ ( i − j + 1 ) si - sj - 1 \geq x * (i-j+1) si−sj−1≥x∗(i−j+1)
等价于
a j + a j + 1 + . . . + a i ≥ x + x + . . . + x aj+aj+1+...+ai\geq x+x+...+x aj+aj+1+...+ai≥x+x+...+x
等价于
( a j − x ) + ( a j + 1 − x ) + . . . + ( a i − x ) ≥ 0 (aj-x)+(aj+1-x)+...+(ai-x)\geq 0 (aj−x)+(aj+1−x)+...+(ai−x)≥0
如果我们设 b i = a i − x bi=ai-x bi=ai−x, b i bi bi的前缀和数组为 T i Ti Ti的话,
上述式子即可转化为:
T i − T j − 1 ≥ 0 Ti-Tj-1\geq0 Ti−Tj−1≥0
如果想要找到这样一个合法区间,我们只需要找到一个平均值最大的区间,看看它是否到达 x x x即可。
因此,我们可以枚举区间右端点 i i i,即固定 T i Ti Ti,要想让 T i − T j − 1 Ti-Tj-1 Ti−Tj−1最大,只要找到最小的 T j − 1 Tj-1 Tj−1即可。该过程可以用 D P DP DP来做。
这个 D P DP DP比较简单,定义 d p i dpi dpi为前 i i i个 T i Ti Ti中最小的一个。
转移方程为: d p i = m i n ( d p i − 1 , T i ) dpi = min(dpi-1,Ti) dpi=min(dpi−1,Ti)
那么我们的 T i − T j − 1 Ti-Tj-1 Ti−Tj−1的最大值即: T i − d p i − F Ti-dpi-F Ti−dpi−F
因为我们是枚举的 i i i,所以这个过程是 O ( n ) O(n) O(n)的。
也就是说我们只需要通过 O ( n ) O(n) O(n)的时间复杂度,就能够判断一个平均值 x x x是否能够达到。
因此,我们只需要去二分这个 x x x即可,即二分答案。
而通过题目,我们发现, x x x的最大值就是2000。
因此,总的时间复杂度就是: O ( n l o g ( 2000 ) ) O(nlog(2000)) O(nlog(2000))
3、代码
cpp
#include<bits/stdc++.h>
#define endl '\n'
#define deb(x) cout << #x << " = " << x << '\n';
#define INF 0x3f3f3f3f
using namespace std;
const int N = 1e5 + 10;
const double eps = 1e-5;
int n, f;
double a[N], T[N], dp[N];
bool check(double mid)
{
for(int i = 1; i <= n; i ++)
T[i] = T[i - 1] + a[i] - mid;
for(int i = 1; i <= n; i ++)
{
dp[i] = min(dp[i - 1], T[i]);
}
for(int i = f; i <= n; i ++)
{
if(T[i] - dp[i-f] >= 0)
return true;
}
return false;
}
void solve()
{
cin >> n >> f;
for(int i = 1; i <= n; i ++)
cin >> a[i];
double l = 0, r = 2000.0;
while(r - l > eps)
{
double mid = (l + r) / 2.0;
if(check(mid))
l = mid;
else
r = mid;
}
cout << (int)(r * 1000) << endl;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
t = 1;
//cin >> t;
while(t--)
solve();
}