联赛模拟测试5 平均数 二分答案+逆序对

题目描述

分析

之前做过类似的两道题,一道是区间和的\(k\)小值,一道是众数的\(k\)小值

那两道统计的东西都有单调性,可以用两个指针维护,\(O(n)\)计算

但是平均数没有单调性,不能用两个指针去扫

但是这道题的数据范围是 \(10^5\),时间限制是\(2.5s\)

统计答案时还可以再套一个\(log\)

如果当前枚举的平均值是\(mid\)的话

我们只需要找出所有\(\frac{sum[j]-sum[i]}{j-i} \leq mid\)的数对即可

我们把分母乘过去,就变成了\(sum[i]-mid \times i \geq sum[j]-mid \times j\)

可以看成区间求逆序对

用树状数组做需要离散化,用归并排序比较方便

代码



#include<cstdio>
#include<queue>
#include<algorithm>
typedef double db;
const int maxn=1e6+5;
const db eps=1e-6;
int n,a[maxn];
long long sum[maxn],m,js;
db b[maxn],c[maxn];
void msort(int l,int r){
	if(l==r) return;
	int mids=(l+r)>>1;
	msort(l,mids);
	msort(mids+1,r);
	int now=l,t=l-1;
	for(int i=mids+1;i<=r;i++){
		while(b[now]<b[i] && now<=mids){
			c[++t]=b[now++];
		}
		js+=mids-now+1;
		c[++t]=b[i];
	}
	while(now<=mids) c[++t]=b[now++];
	for(int i=l;i<=r;i++) b[i]=c[i];
}
bool jud(db mids){
	for(int i=0;i<=n;i++){
		b[i]=sum[i]-i*mids;
	}
	js=0;
	msort(0,n);
	if(js>=m) return 1;
	else return 0;
}
int main(){
	freopen("ave.in","r",stdin);
	freopen("ave.out","w",stdout);
	scanf("%d%lld",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	for(int i=1;i<=n;i++){
		sum[i]=sum[i-1]+a[i];
	}
	double l=-1e9,r=1e9,mids;
	while(r-l>eps){
		mids=(l+r)/2.0;
		if(jud(mids)) r=mids;
		else l=mids;
	}
	printf("%.4f\n",mids);
	return 0;
}