T1
- 这题真的神题吧,看完题目只会最短路和暴力dp,但不幸,一分也没有。
- 题解挂上算了……
T2
- 相比第一题,这个题就有点水了。(不过说实话自己是大力猜结论,合理对拍,成功找到正解结论)。
- 对原序列先从小到大排序。考虑要求平均数-中位数最大值,那么奇数个数的集合肯定比偶数个数的集合更优一些。这就是那个大胆的结论,正是有了它,我们才可以摆脱\(2^n\)枚举的魔咒!
- 我们已经知道了集合个数为奇数,显然可以枚举中位数。比较好想的是,我们枚举了中位数,再枚举一个长度,那么当前最优的取法已经确定。在中位数左边的数去靠右的,在中位数右边的也取靠右的。这样一定是最优的。
- 但是这样是\(n^2\)的,60分。要说暴力分挺足的,但是既然想到了这一步,再想不到二分就有点sb了吧。(实锤我就是那个sb)。你中位数已经确定了,你取的方案也知道了,靠右取。那么随着长度越来越长,一定会有一个分界点,使得平均数开始减小。二分这个分界点,就ok了。复杂度\(n*log_n\)。愉快Ac!
- 代码合理分治数据,稳!
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+10;ll sum[N];
int n,a[N],b[N];double ans;
double c,d;
void check(int top){
int c[N];
for(int i=1;i<=top;++i) c[i]=b[i];
sort(c+1,c+top+1);
double temp;
if(top%2==1) temp=c[top/2+1];
else temp=1.0*(c[top/2]+c[top/2+1])/2;
double res=0;
for(int i=1;i<=top;++i) res+=c[i];
res=1.0*res/top;
ans=max(ans,res-temp);
}
void dfs(int x,int top){
if(x==n+1){
check(top-1);
return ;
}
b[top]=a[x];
dfs(x+1,top+1);
b[top]=0;
dfs(x+1,top);
}
bool check1(int i,int mid){
c=1.0*(sum[i-1]-sum[i-mid-1]+sum[n]-sum[n-mid]+a[i])/(2*mid+1)-a[i];
d=1.0*(sum[i-1]-sum[i-mid]+sum[n]-sum[n-mid+1]+a[i])/(2*mid-1)-a[i];
if(c<d) return 1;
else return 0;
}
int main(){
freopen("subset.in","r",stdin);
freopen("subset.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
if(n<=20){
dfs(1,1);
printf("%.5lf\n",ans);
}else if(n<=2000){
sort(a+1,a+n+1);for(int i=1;i<=n;++i) sum[i]=sum[i-1]+a[i];
for(int i=1;i<=n;++i){//i是当前中位数
for(int l=1;l<=min(i-1,n-i);++l){
ans=max(ans,1.0*(sum[i-1]-sum[i-1-l]+sum[n]-sum[n-l]+a[i])/(2*l+1)-a[i]);
}
}
printf("%.5lf\n",ans);
}else{
sort(a+1,a+n+1);for(int i=1;i<=n;++i) sum[i]=sum[i-1]+a[i];
for(int i=1;i<=n;++i){//i是当前中位数
int l=1,r=min(i-1,n-i);
while(l+1<r){
int mid=l+r>>1;
if(check1(i,mid)) r=mid;
else l=mid;
}
if(check1(i,r)) ans=max(ans,d);
else ans=max(ans,c);
}
printf("%.5lf\n",ans);
}
return 0;
}
T3
看完题目,完了。AC自动机+dp+期望+高斯消元,爆零吧。(哭了)