📅  最后修改于: 2023-12-03 15:07:06.786000             🧑  作者: Mango
给定一个包含 $n$ 个正整数的数组 $a$,以及一个 $k$,你需要在其中选择 $k$ 个数,使得这 $k$ 个数的和的平均值尽可能大。请输出这个最大可能的平均值,保留 $2$ 位小数。
输入:
6 3
1 2 3 4 5 6
输出:
4.50
题目中要求选择 $k$ 个数的和的平均值尽可能大,即要尽可能地选取较大的数。所以,这里可以采用贪心的思想:每次选择数组中最大的 $k$ 个数,计算这 $k$ 个数的和的平均值,然后再逐步缩小选择范围,直到找到最大平均值。具体实现细节见下方代码。
另一种解法是使用二分答案法。根据二分答案的思路,首先可以确定一个平均值 $mid$,然后使用类似于滑动窗口的方法,判断是否能找到 $k$ 个数,使得这 $k$ 个数的和的平均值大于等于 $mid$。逐步缩小平均值的范围,最终得到最大的平均值。具体实现细节见下方代码。
方法一:贪心法的代码实现
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
int a[maxn];
int main()
{
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++) cin>>a[i];
sort(a+1,a+n+1,greater<int>());//对数组进行降序排列
double ans=-1.0;
for(int i=k;i<=n;i++)
{
double sum=0;
for(int j=1;j<=i;j++) sum+=a[j];
ans=max(ans,sum/i);//更新答案
}
printf("%.2lf\n",ans);
return 0;
}
方法二:二分答案法的代码实现
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
int a[maxn];
int main()
{
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++) cin>>a[i];
double l=0,r=100010;//最小平均值为0,最大平均值为100010
while(r-l>=1e-5)//精度控制
{
double mid=(l+r)/2;
double sum=0,mn=0;
bool flag=false;
for(int i=1;i<=n;i++)
{
sum+=a[i]-mid;
if(i>=k)//判断是否达到了k个数
{
mn=min(mn,sum);
if(sum-mn>=0)//说明找到了k个数,平均值大于等于mid
{
flag=true;
break;
}
}
}
if(flag) l=mid;//如果找到了,说明平均值可以更大
else r=mid;//否则需要缩小平均值
}
printf("%.2lf\n",l);
return 0;
}
方法一:贪心法的时间复杂度为 $\mathcal{O}(n\log n+k^2)$,其中 $\mathcal{O}(n\log n)$ 是排序的时间复杂度,$\mathcal{O}(k^2)$ 是求和的复杂度。
方法二:二分答案法的时间复杂度为 $\mathcal{O}(n\log m)$,其中 $m$ 表示平均值的范围,本题中 $m$ 最大为 $10^5$。所以,本方法也是完全可行的。