[BZOJbegin][NOIP十连测第三场]平均数(二分+归并排序求逆序对)

题目描述

[BZOJbegin][NOIP十连测第三场]平均数(二分+归并排序求逆序对)_第1张图片

题解

首先二分一个答案k,将序列中的数都减去k,然后求前缀和。
可以发现平均数小于k的子序列只可能是 Si>Sji<j 的。也就是序列中的逆序对数。
因为是实数二分+判定,用归并排序求逆序对即可。
注意判断精度。

代码

#include
#include
#include
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 (revreturn 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);
}

总结

①减去一个数求前缀和——平均数常用套路。

你可能感兴趣的:(题解,二分,归并)