但,有時候,看起來條件越寬鬆的問題,越容易枚舉
因為考慮的是子集合,序列中元素的先後順序就不重要了,乾脆先排序一下
為了方便說明,令$S_{i}$為排序後序列的第$i$個元素
我們會需要求平均數-中位數的最大值
如果要枚舉,觀察一下枚舉甚麼最適合 (選項:子集合頭、平均數、中位數)
就是中位數啦
這樣就可以對每個中位數,左邊選$k$個數字,右邊也選$k$個數字 (注意此時序列已經排序完成),盡量讓平均最大
假設現在中位數枚舉到$S_{i}$,我們來討論看看最佳的選數字方法是甚麼
如果$k=1$,那麼左邊選$S_{i-1}$,右邊選$S_{N}$,一定是最佳的
如果$k=k$,那麼左邊選$S_{i-k}\sim S_{i-1}$,右邊選$S_{N-k}\sim S_{N}$,一定是最佳的
可以發現,當$k$決定了,就可以很輕易地給出最佳數字選擇方案
當$k$慢慢增加時,最佳數字選擇方案所得到的平均值會有甚麼變化呢?
可以發現,平均值會先升後降
因此可以套用三分搜來尋找這個$k$讓平均值最大,注意$k$可以$=0$
三分搜的詳情請參考code
至於平均值的計算
因為最佳的選數字方法會選擇連續的兩段數字,可以考慮預處理前綴和,然後兩段連續數字和就可以用前綴和相減的方式求出,相加之後再除以總數就是平均值
枚舉$i$,三分搜找出最佳$k$並更新答案
這樣就完成了嗎?
好像還沒耶
中位數有可能是中間兩數的平均
因此需要再枚舉一次相鄰兩數的平均當中位數的情況 (這時相鄰兩數可以合併作一個權重2的數字看待)
也符合上述的三分搜單調性,依照上述做法再做一次就好
時間複雜度:$O(N\log N)$
code:
#include<cstdio> #include<cassert> #include<algorithm> using namespace std; typedef long long LL; int N; LL S[200001],SUM[200001]; double ANS_BEST; int ANS_L1,ANS_L2,ANS_R1,ANS_R2; void Update(const double &ans_best,const int ans_l1,const int ans_l2,const int ans_r1,const int ans_r2) { if(ans_best<=ANS_BEST)return; ANS_BEST=ans_best,ANS_L1=ans_l1,ANS_L2=ans_l2,ANS_R1=ans_r1,ANS_R2=ans_r2; } double OddAns(const int mid,const int k) { return (double)((SUM[mid]-SUM[mid-k-1])+(SUM[N]-SUM[N-k]))/(k*2+1)-S[mid]; } double EvenAns(const int mid,const int k) { return (double)((SUM[mid+1]-SUM[mid-k-1])+(SUM[N]-SUM[N-k]))/(k*2+2)-0.5*(S[mid]+S[mid+1]); } void Solve(vector<int>&ans) { ANS_BEST=-1.0; sort(S+1,S+N+1); for(int i=1;i<=N;i++)SUM[i]=SUM[i-1]+S[i]; for(int i=1;i<=N;i++) { int l=0,r=min(i-1,N-i); while(r-l>=3)//三分搜 { const int ml=(l*2+r)/3,mr=(l+r*2)/3; if(OddAns(i,ml)<OddAns(i,mr))l=ml; else r=mr; } for(int k=l;k<=r;k++)assert(k>=0),Update(OddAns(i,k),i-k-1,i,N-k,N); } for(int i=1;i<N;i++) { int l=0,r=min(i-1,N-(i+1)); while(r-l>=3)//三分搜 { const int ml=(l*2+r)/3,mr=(l+r*2)/3; if(EvenAns(i,ml)<EvenAns(i,mr))l=ml; else r=mr; } for(int k=l;k<=r;k++)assert(k>=0),Update(EvenAns(i,k),i-k-1,i+1,N-k,N); } ans.clear(); assert(ANS_L1<ANS_L2); for(int i=ANS_L1+1;i<=ANS_L2;i++)ans.push_back(S[i]); for(int i=ANS_R1+1;i<=ANS_R2;i++)ans.push_back(S[i]); } int main() { // freopen("in.txt","r",stdin); while(scanf("%d",&N)==1) { for(int i=1;i<=N;i++)scanf("%I64d",&S[i]); vector<int>ans; Solve(ans); printf("%d\n",(int)ans.size()); for(int i=0;i<(int)ans.size();i++) { if(i)putchar(' '); printf("%d",ans[i]); } puts(""); } return 0; }
沒有留言:
張貼留言
歡迎留言或問問題~
若您的留言中包含程式碼,請參考這篇
如果留言不見了請別慌,那是因為被google誤判成垃圾留言,小莫會盡快將其手動還原
注意:只有此網誌的成員可以留言。