AcWing 102. 最佳牛围栏(前缀和+二分+DP)

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 ] s[i] s[i]

这道题我们可以换个思路想,假设给你一个平均值 x x x。我们要做的是判断这个平均值是否能够达到。

换句话说,我们就是要判断是否存在一个区间,使得这个区间和的平均值达到了 x x x

将其转化为数学表达式即:

是否存在一个区间 [ i , j ] [i,j] [i,j],其中 i − j + 1 > = F i-j+1>=F ij+1>=F,满足:
s [ i ] − s [ j − 1 ] i − j + 1 ≥ x \frac{s[i] - s[j - 1]}{i-j+1} \geq x ij+1s[i]s[j1]x
等价于
s [ i ] − s [ j − 1 ] ≥ x ∗ ( i − j + 1 ) s[i] - s[j - 1] \geq x * (i-j+1) s[i]s[j1]x(ij+1)
等价于
a [ j ] + a [ j + 1 ] + . . . + a [ i ] ≥ x + x + . . . + x a[j]+a[j+1]+...+a[i]\geq x+x+...+x a[j]+a[j+1]+...+a[i]x+x+...+x
等价于
( a [ j ] − x ) + ( a [ j + 1 ] − x ) + . . . + ( a [ i ] − x ) ≥ 0 (a[j]-x)+(a[j+1]-x)+...+(a[i]-x)\geq 0 (a[j]x)+(a[j+1]x)+...+(a[i]x)0

如果我们设 b [ i ] = a [ i ] − x b[i]=a[i]-x b[i]=a[i]x b [ i ] b[i] b[i]的前缀和数组为 T [ i ] T[i] T[i]的话,

上述式子即可转化为:
T [ i ] − T [ j − 1 ] ≥ 0 T[i]-T[j-1]\geq0 T[i]T[j1]0

如果想要找到这样一个合法区间,我们只需要找到一个平均值最大的区间,看看它是否到达 x x x即可。

因此,我们可以枚举区间右端点 i i i,即固定 T [ i ] T[i] T[i],要想让 T [ i ] − T [ j − 1 ] T[i]-T[j-1] T[i]T[j1]最大,只要找到最小的 T [ j − 1 ] T[j-1] T[j1]即可。该过程可以用 D P DP DP来做。

这个 D P DP DP比较简单,定义 d p [ i ] dp[i] dp[i]为前 i i i T [ i ] T[i] T[i]中最小的一个。

转移方程为: d p [ i ] = m i n ( d p [ i − 1 ] , T [ i ] ) dp[i] = min(dp[i-1],T[i]) dp[i]=min(dp[i1],T[i])

那么我们的 T [ i ] − T [ j − 1 ] T[i]-T[j-1] T[i]T[j1]的最大值即: T [ i ] − d p [ i − F ] T[i]-dp[i-F] T[i]dp[iF]

因为我们是枚举的 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、代码

#include
#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();
}

你可能感兴趣的:(#,基础算法与数论题目,算法)