给出n个数的集合,求一个 (平均数-中位数)最大 (偏度最大)的子集,输出子集元素个数和各个元素(任意顺序)。
因为是子集,所以不一定是连续的序列。然后我们有下面几个结论。
1.最大偏度一定≥0
因为一个元素时,偏度为0。
2.最大偏度子集必定有元素个数为奇数个的。
证:
如果当元素个数是偶数2*k时偏度最大,我们证明它去掉一个元素a[k+1]不会更差。
子集里排好序分别是a[i]。除去a[k+1]其它数的平均值为av
新平均值-旧平均值=av-(av+a[k+1])/2=(av-a[k+1])/2
新中位数-旧中位数=a[k]-(a[k]+a[k+1])/2=(a[k]-a[k+1])/2
且有 旧平均值-旧中位数=(av+a[k+1])/2-(a[k]+a[k+1])/2=(av-a[k])/2≥0 (否则不可能偏度最大)
所以有 平均值增量-中位数增量=(av-a[k])/2≥0
所以新的偏度肯定不会更差。
3.平均值先递增后递减
因为是奇数个,所以枚举每个数做中位数,假如左右延伸长度为j,那么要使偏度更大,我们一定是每次选剩下的里面左边最大和右边最大的数。所以剩下的数越来越小,平均值增加得越来越少,而当前平均值越来越大,到某个峰值后平均值就开始减小了。
所以可以用二分法每次取中点和中点旁边一个点判断当前平均值在增加还是减小,增加就往右找,减小就往左找。
注意:浮点数判断大小不安全,所以要转换为乘法。还有一种方法我不太理解就不说了。
#include<cstdio>
#include<algorithm>
#define N 200005
#define ll long long
using namespace std;
ll a[N],s[N];
int n,ansi=1,ansp;
double ans;
//最大偏度≥0,所以初始值就是只有第一个元素,偏度为0。
ll sum(int i,int j)
{
return s[n]-s[n-j]+ s[i]-s[i-j-1] ;
}
int main()
{
scanf("%d",&n);
for(int i=1; i<=n; i++)
{
scanf("%I64d",&a[i]);
}
sort(a+1,a+1+n);
for(int i=1; i<=n; i++)
{
s[i]=s[i-1]+a[i];
}
for(int i=2; i<n; i++)
{
int l=1,r=min(i-1,n-i),m;
ll s1,s2;
while(l<r)
{
m=l+(r-l)/2;
s1=sum(i,m)*(2*m+3);
s2=sum(i,m+1)*(2*m+1);
if (s1>s2)
{
r=m;
}
else
{
l=m+1;
if(s1==s2)
{
break;
}
}
}
if(1.0*sum(i,l)/(2*l+1)-a[i]>ans)
{
ans=1.0*sum(i,l)/(2*l+1)-a[i];
ansi=i;
ansp=l;
}
}
printf("%d\n%I64d ",ansp*2+1,a[ansi]);
for(int i=1; i<=ansp; i++)
{
printf("%I64d %I64d ",a[ansi-i],a[n-i+1]);
}
return 0;
}