P1404 平均数

描述:
(给一个长度为 n 的数列,我们需要找出该数列的一个子串,使得子串平均数最大化,并且子串长度 ≥m。)

题目传送门:平均数

题目算法:二分+DP+思维

分割线

又是束手无策的一道题目,理解起来也很反人类.......以下见解摘自这位dalao

Ⅰ.如何进行二分

二分最大平均值,然后进行验证。如何验证?将序列中的元素减去平均值

如果有一段长度不小于L的区间和大于等于0,说明该平均值可行,调整边界,继续二分。

Ⅱ.如何求有限制性最大子段和

方法1

求一个子段,它的和最大,没有“长度不小于L”这个限制。这个可以用动态规划解决。 (d[i]=max(d[i-1]+A[i],0.0));

(d[i])为以i结尾的最大子段和

如何将“限制性”转化为一般情况:

(s[i])为以i结尾且长度不小于L的最大子段和,(A[i])为原数组,(sum[i])为前缀和。

因为长度要不小于L,所以从(A[i-L])一直到(A[i])是必选的,总和为(sum[i]-sum[i-L]),前面(i-L)个数可选可不选

要想和最大,就要加上以(a[i-L])结尾的最大子段和,所以(s[i]=sum[i]-sum[i-L]+d[i-L]) (前面(i-L)个数的最大子段和)

最后在(s[i])中取一个最大值就是(ans)。也可以不用s数组,直接在过程中取最大值

方法2

最大子段和可以转化为前缀和相减的形式。

设sum[i]为序列A的前i项的和 ,设s[i]是序列A以A[i]结尾且长度不小于L的最大连续子段和

那么显然有: (s[i] = sum[i] - min){(sum[j])}((0<=j<=i-L))

这里的代码用的是第一种方法。

#include <bits/stdc++.h>
using namespace std;
const int maxn=100009;
const double eps=1e-7;
int n,m;
double a[maxn],b[maxn],dp[maxn],sumn[maxn];
bool check(double mid)
{
	for(int i=1;i<=n;i++)
	{
		b[i]=a[i]-mid;
		sumn[i]=sumn[i-1]+b[i];
		dp[i]=max(dp[i-1]+b[i],0.0);
	}
	for(int i=m;i<=n;i++)
		if(dp[i-m]+sumn[i]-sumn[i-m]>=0)	return true;
	return false;
}
int main()
{
	scanf("%d%d",&n,&m);
	double l=0,r=0,mid;
	for(int i=1;i<=n;i++)
	{
		scanf("%lf",&a[i]);
		r=max(r,a[i]);
	}
	while(r-l>eps)
	{
		mid=(l+r)/2.0;
		if(check(mid))	l=mid;
		else	r=mid;
	}
	printf("%d",int(r*1000));
}
原文地址:https://www.cnblogs.com/iss-ue/p/12579227.html