题目描述
题解
首先二分一个答案k,将序列中的数都减去k,然后求前缀和。
可以发现平均数小于k的子序列只可能是
Si>Sj且i<j
的。也就是序列中的逆序对数。
因为是实数二分+判定,用归并排序求逆序对即可。
注意判断精度。
代码
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define N 100005
#define LL long long
const double eps=1e-6;
int n;
LL k,rev;
double Max,ans;
double a[N],b[N],c[N],s[N];
int dcmp(double x)
{
if (x<=eps&&x>=-eps) return 0;
if (x>0) return 1;
else return -1;
}
void mergesort(int l,int r)
{
if (l==r) return;
int mid=(l+r)>>1;
mergesort(l,mid);
mergesort(mid+1,r);
int i=l,j=mid+1,now=l-1;
while (i<=mid&&j<=r)
{
if (dcmp(s[j]-s[i])>=0) c[++now]=s[i],i++;
else c[++now]=s[j],j++,rev+=mid-i+1;
}
while (i<=mid) c[++now]=s[i],i++;
while (j<=r) c[++now]=s[j],j++;
for (int i=l;i<=r;++i) s[i]=c[i];
}
bool check(double x)
{
for (int i=1;i<=n;++i) b[i]=a[i]-x;
memset(s,0,sizeof(s));
for (int i=1;i<=n;++i) s[i]=s[i-1]+b[i];
rev=0;
for (int i=1;i<=n;++i) if (dcmp(s[i])<=0) rev++;
mergesort(1,n);
if (rev<k) return false;
else return true;
}
double find()
{
double l=0,r=Max,mid,ans;
while (dcmp(r-l)!=0)
{
mid=(l+r)/(2.0);
if (check(mid)) ans=mid,r=mid;
else l=mid;
}
return ans;
}
int main()
{
scanf("%d%lld",&n,&k);
for (int i=1;i<=n;++i) scanf("%lf",&a[i]),Max=max(Max,a[i]);
ans=find();
printf("%0.4lf\n",ans);
}
总结
①减去一个数求前缀和——平均数常用套路。