最大平均数子数组及最大连续和(一点升级的前缀和)

1.最大连续和问题

给你一个长度为N的整数序列 {A1,A2,…,An},要求从中找出一段连续的长度不超过M且不为0的子序列,使得这个序列的和最大。

样例输入

6 4
1 -3 5 1 -2 3

样例输出

7

数据范围与提示
1<=N,M<=2e5

当看到这道题的第一眼,直接暴力枚举好吧
然而这道题其实一看就是一个前缀和, 但不是一个简单的前缀和。

WA代码(45分)

#include <bits/stdc++.h>
using namespace std;

const int M = 2e5 + 5;
int a, n, m, ans = INT_MIN, len;
int b[M];
int main() {
    cin >> n >> m;
    for (int i = 0; i < n; i++) {
        cin >> a;
        if (b[i - 1] > 0)
            b[i] = b[i - 1] + a, len++;
        else
            b[i] = a, len = 1;
        if (b[i] > ans && len <= m)
            ans = b[i];
    }
    cout << ans << endl;
    return 0;
}

这种做法一眼开过去似乎并没有什么问题,BUT,这样做就有可能会漏掉一些组合。
所以这道题的正解是用双指针。

AC代码

#include <bits/stdc++.h>
using namespace std;

const int M=2e5+5;
int n,m,x,head=1,tail,ans=INT_MIN;
int dp[M],q[M];

int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>x;
		dp[i]=dp[i-1]+x;		//dp记录前缀和
	}
	for(int i=0;i<=n;i++){					//计算以i结尾的最大连续和
		while(q[head]+m<i && tail>=head) head++;				//保证q[head]与i在m范围之内,q用来记录一段连续子段和的值最小的起点下标
		while(dp[i]<dp[q[tail]] && tail>=head) tail--;			//从后往前找第一个小于等于dp[i]的位置
		if(i!=0) ans=max(ans,dp[i]-dp[q[head]]);				//q[head]开始到i的子段和的长度是在m之内的,更新ans
		q[++tail]=i;			//在tail+1的位置记录i
	}
	cout<<ans<<endl;
	return 0;
}

这样做的话,就是先求出前缀和,再用双指针来求起点和终点。这个q数组就是用来保存一段连续子段和的值最小的起点下标。而i是用来枚举终点。
但需要注意的是head和tail是作为一个相向的,并且要将其作为一个q数组的下标

2.最大平均数子数组

就是给一个数列a_1,a_2,…a_n,求它的一个连续子数列。求长度至少为k,平均值最大的一段子数列的平均值。
为了避免误差,将结果向下取整输出即可。(即 (int)(ans*1000))

样例输入

10 6
6 4 2 10 3 8 5 9 4 1

样例输出

6500

数据范围与提示
1<=N<=100000
-2000<=Ai<=2000
1<=K<=N

这道题的思路就是二分答案,但还是需要用到前缀和。

AC代码

#include <bits/stdc++.h>
using namespace std;

const int M=1e5+5;
const double esp=1e-6;
int n,k;
double a[M],dp[M],f[M];
double l,r,mid;

bool check(double mid){
	double ans=-1,mi=2005;			
	for(int i=1;i<=n;i++){
		dp[i]=a[i]-mid;			//将平均数减掉,会得到正权值和负权值
		f[i]=f[i-1]+dp[i];		//前缀和
	}
	for(int i=k;i<=n;i++){		//枚举长度>=k的子序列
		mi=min(mi,f[i-k]);		//mi得到前面一段
		ans=max(ans,f[i]-mi);		//ans将前面的前缀和减掉后,看是否大于0
	}
	return ans>=0;			//如果ans大于等于0,那就说明答案比它大,移动l
}

int main(){
	cin>>n>>k;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	l=-1e9,r=1e9;
	while(r-l>esp){
		mid=(l+r)/2;
		if(check(mid)) l=mid;
		else r=mid;
	}
	cout<<int(r*1000);
	return 0;
}

这个二分找平均数有几个需要注意的点

  • 首先,在check函数刚开始时,需要算出前缀和的同时减掉枚举出来的mid,如果和是大于mid就是一个正权值,小于mid就是一个负权值
  • 之后,mi变量就是来寻找前缀和里面最小的一个,而那个范围就是从至少的长度k开始。
  • ans如果减掉前缀和-mi之后,还是大于等于0的话,那就说明正解还比mid大,所以就可以移动l,否则就只能移动r。

总结

这两道题都是用前缀和加上一些其他的算法来完成的,而这些题都有些共同的特点,比如求最大,但有范围限制。
最后,我有一个提醒,因为我有一个朋友因为这个改了n小时,那就是极值的赋值
而那个朋友就是@MAGICAL DEER

还有祝大家五一节快乐

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
几何平均数最大数组的问题可以通过使用前缀积来解决。前缀积是指给定一个长度为n的数组arr,其前缀积数组prod为prod[i] = arr[0]*arr[1]*...*arr[i-1]*arr[i],其中prod[0]=1。接下来,我们可以使用滑动窗口算法来找到长度为k的数组,其几何平均数最大。 具体而言,我们可以使用两个指针i和j,分别表示数组的左右端点。然后,我们将j移动到第k个位置,并计算窗口内元素的几何平均数。接着,我们将i指针向右移动,直到保证窗口大小为k为止。在每个i和j的组合中,我们可以计算出当前窗口内元素的几何平均数,并将其与现有最大值进行比较。如果当前几何平均数大于先前的最大值,则将其更新为最大值。 C++代码示例如下: ``` #include <iostream> #include <cmath> using namespace std; double findMaxGeoMean(int arr[], int n, int k) { double maxGeoMean = 0.0; double prod = 1.0; int i = 0, j = 0; while (j < k) { prod *= arr[j]; j++; } maxGeoMean = pow(prod, 1.0 / k); while (j < n) { prod *= arr[j]; prod /= arr[i]; maxGeoMean = max(maxGeoMean, pow(prod, 1.0 / k)); i++; j++; } return maxGeoMean; } int main() { int arr[] = { 1, 2, 3, 4, 5 }; int n = sizeof(arr) / sizeof(arr[0]); int k = 3; double maxGeoMean = findMaxGeoMean(arr, n, k); cout << "The maximum geometric mean is " << maxGeoMean << endl; return 0; } ``` 总结: 几何平均数最大数组问题可以通过滑动窗口算法和前缀积来解决。我们可以使用两个指针来遍历数组。在每一个窗口中,我们可以计算出当前窗口内元素的几何平均数,并将其与现有最大值进行比较。如果当前几何平均数大于先前的最大值,则将其更新为最大值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值