题目大意
给出长度为n的序列,求所有子区间的和中,第k小的值是多少。
n<=100000
时间限制 1s
空间限制 256M
解题思路
二分答案,每次把序列全部减去mid,问题就变成了求有多少个子区间的和是负数,即sum[i-1]>sum[j],i<=j,求逆序对就可以了。
用线段树会超时,只能用归并排序。
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 100006
#define fr(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const double eps=0.00001;
int i,n;
ll k,tot;
double l,r,mid,ans,a[maxn],b[maxn],sum[maxn];
void merge(int l,int r)
{
int i,mid=(l+r) >> 1,x=l,y=mid,w=mid+1,v=r;
fr(i,l,r) b[i]=sum[i];
i=x-1;
while (x<=y && w<=v)
if (b[x]<=b[w])
sum[++i]=b[x++];
else
{
tot+=mid-x+1;
sum[++i]=b[w++];
}
while (x<=y && w>v) sum[++i]=b[x++];
while (w<=v && x>y) sum[++i]=b[w++];
return;
}
void sort(int l,int r)
{
if (l==r) return;
int mid=(l+r) >> 1;
sort(l,mid);
sort(mid+1,r);
merge(l,r);
return;
}
ll check(double mid)
{
int i;
tot=sum[0]=0;
fr(i,1,n) a[i]-=mid;
fr(i,1,n) sum[i]=sum[i-1]+a[i];
sort(0,n);
fr(i,1,n) a[i]+=mid;
return tot;
}
int main()
{
freopen("ave.in","r",stdin);
freopen("ave.out","w",stdout);
scanf("%d%lld",&n,&k);
l=1 << 30,r=0;
fr(i,1,n)
{
scanf("%lf",&a[i]);
l=min(l,a[i]),r=max(r,a[i]);
}
while (r-l>=eps)
{
mid=(l+r)/2;
ll t=check(mid);
if (t<k) l=mid+eps;
else r=mid;
}
printf("%.4f\n",l);
return 0;
}