题目
思路
好像非常麻烦的题。没有任何思路。
但是,考虑一共选择偶数个的情况。可以将每个数都减去一个值,使得中位数为
r
2
\frac{r}{2}
2r 。假设其他的数的和为
S
S
S 。形如 x x 0 r x x
(排序后结果)。
设其个数为 n + 1 n+1 n+1 。考虑去掉 r r r ,新的权值是 S n \frac{S}{n} nS ,而原来的权值是 S + r n + 1 − r 2 \frac{S+r}{n+1}-\frac{r}{2} n+1S+r−2r 。显然在 S + r < 0 S+r<0 S+r<0 时,不优。否则容易判断
S + r n + 1 − r 2 ≤ S n + 1 ≤ S n \frac{S+r}{n+1}-\frac{r}{2}\le\frac{S}{n+1}\le\frac{S}{n} n+1S+r−2r≤n+1S≤nS
所以去掉 r r r 更优。至此,我们证明了,最优的解一定是奇数个。(另:据 H a n d I n D e v i l \sf HandInDevil HandInDevil 说,必须是相邻两个数产生中位数,这东西也很好证明。)
那么,中位数一定是某一个数,可以枚举了!枚举之后,只需要使平均数最大。考虑一对一对的加入数字。
显然加入的两个数字只会越来越小,所以平均数的导数一定单减。因为一个集合的平均数在任意划分的两个组成部分的平均数之间。
于是三分解决。当然,也可以二分法求导数的零点。
代码
这里给出三分版的代码。
#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
inline int readint(){
int a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
template < typename T >
void getMax(T&a,T b){ if(a < b) a = b; }
template < typename T >
void getMin(T&a,T b){ if(b < a) a = b; }
const int MaxN = 200005;
int a[MaxN], n;
long long s[MaxN];
struct Frac{
long long a, b;
Frac(long long A,long long B){
a = A, b = B;
}
bool operator < (const Frac &that) const {
if(b*that.b < 0)
return a*that.b > b*that.a;
return a*that.b < that.a*b;
}
};
Frac evaluate(int i,int x){
long long now = s[i]-s[i-x-1];
now += s[n]-s[n-x];
now -= (2*x+1ll)*a[i]; // 减中位数
return Frac(now,2*x+1ll);
}
int main(){
n = readint();
for(int i=1; i<=n; ++i)
a[i] = readint();
sort(a+1,a+n+1);
for(int i=1; i<=n; ++i)
s[i] = s[i-1]+a[i];
Frac ans(0,1);
int res[2] = {1,0};
for(int i=1; i<=n; ++i){
int L = 0, R = min(n-i,i-1);
while(L != R){
int l = L+(R-L)/3;
int r = R-(R-L)/3;
Frac zuo = evaluate(i,l);
Frac you = evaluate(i,r);
if(zuo < you) L = l+1;
else R = r-1;
}
if(ans < evaluate(i,L)){
ans = evaluate(i,L);
res[0] = i, res[1] = L;
}
}
printf("%d\n",res[1]*2+1);
for(int i=1; i<=res[1]; ++i)
printf("%d %d ",a[res[0]-i],a[n+1-i]);
printf("%d\n",a[res[0]]);
return 0;
}