平均数 题解【二分+求逆序对】

来源:互联网 发布:淘宝客自动转换工具 编辑:程序博客网 时间:2024/05/29 07:16

【问题描述】

有一天,小A得到了一个长度为n的序列。他把这个序列的所有连续子序列都列了出来,并对每一个子序列都求了其平均值,然后他把这些平均值写在纸上,并对它们进行排序,最后他报出了第k小的平均值。你要做的就是模仿他的过程。

【输入格式】

第一行两个整数n,k,意义如题中所述。
第二行n个正整数,即为小A得到的序列。

【输出格式】

一行一个实数,表示第k小的平均值,保留到小数点后4位。

【样例输入输出】

这里写图片描述

【数据范围与约定】

对于40%的数据,n≤1000
对于100%的数据,n≤100000,k≤n*(n+1)/2,序列中的数≤10^9

【解题思路】

第k大不易直接求,我们想到二分,则原问题转变为求区间平均值小于x的区间数量。考虑把序列中的每个数减去x,则我们只需求区间和小于0的区间数量。我们对这个序列求前缀和,则区间[l,r]和小于0当且仅当Sl-1>Sr,答案即为前缀和序列S的逆序对数量,使用经典的归并排序即可解决,时间复杂度O(nlog^2n)。
对于精度问题,可以把数据*100000,最后再除以100000.0即可

【AC 代码】

#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<cmath>#define LL long long using namespace std;const int maxn=100005;LL n,m,num,mina,maxa;LL a[maxn],s[maxn],c[maxn];LL merge(int l,int mid,int r){    int i=l,j=mid+1,k=l;    LL tmp[maxn];    LL sum=0;    while (i<=mid && j<=r)    {        if (s[i]>s[j])        {            tmp[k++]=s[j++];            sum+=mid-i+1;        }else        {            tmp[k++]=s[i++];        }    }    while (i<=mid) tmp[k++]=s[i++];    while (j<=r) tmp[k++]=s[j++];    for (int i=l;i<=r;i++) s[i]=tmp[i];    return sum;}LL mergesort(int l,int r){    LL tmp=0;    if (l<r)    {        int mid=(l+r)/2;        tmp+=mergesort(l,mid);        tmp+=mergesort(mid+1,r);        tmp+=merge(l,mid,r);    }    return tmp;}LL work(LL p){    s[1]=0;    for (int i=1;i<=n;i++)      s[i+1]=s[i]+a[i]-p;    LL sum=mergesort(1,n+1);//归并求逆序对    return sum;}int main(){       scanf("%lld%lld",&n,&m);    mina=100000000000005;    maxa=0;    for (int i=1;i<=n;i++)    {      scanf("%lld",&a[i]);      a[i]*=100000;      mina=min(mina,a[i]);      maxa=max(maxa,a[i]);    }    LL mid,l=mina,r=maxa;    while (l+1<r)//l+1<r是规范写法,其他容易WA    {        mid=(l+r)/2;        if (work(mid)>=m)        {          r=mid;        }else          l=mid;    }    double ans=r*1.0/100000;    printf("%.4lf\n",ans);    return 0;}

【小技巧】

  1. 有单调性考虑二分
  2. 转换思想(如第k大转换为比x小的有多少,同减平均数,区间<0则找逆序对)
  3. 求第k大转换为枚举某个数,比它小的有多少个
  4. 二分判断为l+1
0 0
原创粉丝点击