http://codeforces.com/contest/626/problem/E
题意: 给你n个数,让你选一个非空子集,使得子集中所有数的平均数减去 中位数 这个值最大。
排序后,枚举每一个数位中位数,
对于第i个数作为中位数的情况,我们要使得平均数尽可能大,就是在i的左右对称选x个数,显然越大越好,左边选的是靠近i,右边选的是靠近n。
如何在logn内选出这个X:
如果x=1;也就是选 a[i-1] 和 a[n] 会使得平均数增大,那么当x=2时,显然 a[i-2] 与a[n-1]的和比上一对要小,但是如果他们仍大于2*当前平均数的话,仍然可以拉高平均数,每次拉高的量越来越少,由于是有序的,每次的
a[i-k]+a[n-k+1]的和都是逐渐变小,而平均数是渐渐变大的。
可以知道 随着x的增大,平均数的值应该是 先单调增加 后 单调减少。【也就是所谓bitonic 双调的】,本来应该用三分,但是由于都是整点,我们可以二分找到那个使得平均数最大的X即可
二分的判断就是,判断avg(X)>avg(x-1) 即可
#include <cstdio> #include <cmath> #include <cstring> #include <string> #include <algorithm> #include <queue> #include <map> #include <set> #include <vector> #include <iostream> using namespace std; __int64 mod=1000000000+7; __int64 min(__int64 a,__int64 b) {return a<b?a:b;} __int64 max(__int64 a,__int64 b) {return a>b?a:b;} __int64 n,m; __int64 tm[200005]; __int64 s[200005]; double eps=1e-8; __int64 fz,fm; __int64 bin(__int64 i,__int64 x) { __int64 ll=s[i-1]-s[i-x-1]; __int64 rr=s[n]-s[n-x]; fz=ll+rr+tm[i]; fm=2*x+1; __int64 last_fz=s[i-1]-s[i-1-(x-1)]+s[n]-s[n-(x-1)]+tm[i]; __int64 last_fm=2*x-1; if (fz*last_fm-last_fz*fm>0) return 1; //避免double丢失精度 else return 0; } int main() { __int64 i,j; scanf("%I64d",&n); for (i=1;i<=n;i++) scanf("%I64d",&tm[i]); sort(tm+1,tm+1+n); for (i=1;i<=n;i++) s[i]=s[i-1]+tm[i]; __int64 ansfz,ansfm=0; __int64 ans_i=0; __int64 ans_x=0; for (i=1;i<=n;i++) { __int64 l=0; __int64 r=min(i-1,n-i); __int64 x; while(l<=r) { if (r-l<=1) { if (bin(i,r)) x=r; else x=l; bin(i,x);//记得要更新fz,fm break; } __int64 mid=(l+r)>>1; if (bin(i,mid)) l=mid; else r=mid-1; } fz-=fm*tm[i]; if (ansfm==0||fz*ansfm-fm*ansfz>0 ) { ansfz=fz; ansfm=fm; ans_i=i; ans_x=x; } } if (ans_i==0) { printf("1\n"); printf("%I64d\n",tm[1]); return 0; } printf("%I64d\n",ans_x*2+1); printf("%I64d",tm[ans_i]); for (i=ans_i-1;i>=ans_i-ans_x;i--) printf(" %I64d",tm[i]); for (i=n;i>=n-ans_x+1;i--) printf(" %I64d",tm[i]); printf("\n"); return 0; }