题意:
给你一堆无序数,寻找它的一个子堆,使得子堆的平均数减中位数最大。
数字的个数n<=2e5 0<=xi<=1e6.
思路:
首先可以证明这堆数一定是奇数个,证明方法是尝试在奇数个的有序数列中加入一个数字求平均值和中位数各增加了多少。然后比较一下。
也可以考虑偶数个的序列去掉中间两个中较大的数,差值不会减小。
所以中位数一定是原先堆里的数,我们可以枚举每一个数,然后二分查找范围。
二分查找范围的原理是,随着字串长度的增加,那么差值是先增大后减小的,所以我们枚举某点和它相邻的点的斜率(大小关系),然后进行二分处理,寻找最大值。
坑点:
浮点数如果需要除法比较,把等式两边同时乘上除数的最大公约数,否则会有精确度的问题。
#include<bits/stdc++.h> using namespace std; double jilu[200050]; double bf[200050]; double tans=0; int pos=0,len=0,cet; double ans; int n; double average(int m){ double sum=bf[cet+1]-bf[cet-m]+bf[n]-bf[n-m]; return sum; } void bSearch(int l,int r){ int m; int tmpr=r; while(l<=r){ int m=(l+r)>>1; if(average(m)*(2*(m+1)+1)>=average(m+1)*(2*m+1))r=m-1; else l=m+1; } for(int i=max(0,l-1);i<=min(l+1,tmpr);i++){//二分不一定查找到最大的点,所以进行下调整 if(average(i)-jilu[cet]*(2*i+1)>tans*(2*i+1)){ tans=average(i)/(2*i+1)-jilu[cet]; pos=cet; len=i; } } } int main() { scanf("%d",&n); for(int i=0;i<n;i++){ scanf("%lf",&jilu[i]); } sort(jilu,jilu+n); for(int i=1;i<=n;i++){ bf[i]=bf[i-1]+jilu[i-1]; } for(int i=0;i<n;i++){ cet=i; bSearch(0,min(i,n-1-i)); } //printf("%d %d ",pos,len); printf("%d ",2*len+1); for(int i=pos;i>=pos-len;i--){ printf("%.0lf ",jilu[i]); } for(int i=n-1;i>n-1-len;i--){ printf("%.0lf ",jilu[i]); } }