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

文章讨论了如何优化暴力解法,通过前缀和和动态规划减少区间和查询的时间复杂度,最终采用二分查找策略来解决‘最佳牛围栏’问题,目标是判断给定平均值能否达到区间和的条件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

AcWing 102. 最佳牛围栏

1、问题

2、分析

(1)暴力做法

看到这道题以后,我们可以先想一个最暴力的做法,就是我们去枚举所有长度至少为FFF的区间,然后求出这个区间的和,再求出这个区间的平均值。最后在这些平均值之间取一个最大值。

那么这个暴力做法的时间复杂度是多少呢?枚举所有符合长度要求的区间,该过程在最坏条件下的复杂度是O(n2)O(n^2)O(n2),求出区间的和,复杂度是O(n)O(n)O(n)。那么总的时间复杂度就是O(n3)O(n^3)O(n3)

很明显这个做法是超时的,那么对于这个暴力的做法,我们可以给出一个小小的优化,即我们通过前缀和算法将求区间的时间复杂度优化到O(1)O(1)O(1),优化后,该做法的时间复杂度是O(n2)O(n^2)O(n2)。优化后依旧是超时的。

(2)二分答案+前缀和+DP

受朴素做法的启发,我们先求一下前缀和,将前缀和数组记作:s[i]s[i]s[i]

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

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

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

是否存在一个区间[i,j][i,j][i,j],其中i−j+1>=Fi-j+1>=Fij+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]−xb[i]=a[i]-xb[i]=a[i]xb[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

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

因此,我们可以枚举区间右端点iii,即固定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]即可。该过程可以用DPDPDP来做。

这个DPDPDP比较简单,定义dp[i]dp[i]dp[i]为前iiiT[i]T[i]T[i]中最小的一个。

转移方程为:dp[i]=min(dp[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]−dp[i−F]T[i]-dp[i-F]T[i]dp[iF]

因为我们是枚举的iii,所以这个过程是O(n)O(n)O(n)的。

也就是说我们只需要通过O(n)O(n)O(n)的时间复杂度,就能够判断一个平均值xxx是否能够达到。

因此,我们只需要去二分这个xxx即可,即二分答案。

而通过题目,我们发现,xxx的最大值就是2000。

因此,总的时间复杂度就是:O(nlog(2000))O(nlog(2000))O(nlog(2000))

3、代码

#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();
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值