Robert 的军队
题目描述
Winter is coming.
Robert 是个昏庸的君主,整日沉迷于吃喝玩乐,终于,当寒冬降临,他不得不组
织军队来对抗敌人。
尽管如此,他仍然是个喜欢玩耍的人,还有点强迫症,他希望选出的军队的所有
人的身高的方差最小,并且选出的人数 x 要满足 L<=x<=R,因为太少了他怕不够
气势,太多了他怕会控制不当。
现在给出 n 个士兵的高度,L 和 R,Robert 找到了聪明的你,希望你帮他解决这
个难题。
输入
第一行三个整数,依次是 n,L,R
第二行 n 个正整数,表示每个士兵的高度 h
输出
一行,表示最小的方差,保留三位小数即可。
样例输入
5 3 4
3 2 4 1 4
样例输出
0.222
样例解释
数据范围
对于20%的数据,1<=n<=20
对于50%的数据,1<=n<=2000,R-L+1<=2000
对于100%的数据,1<=L<=R<=n<=100000,1<=h[i]<=1000000000
【题解】
20%
1<=n<=20显然直接枚举每个士兵选不选,时间O(n*2^n)
50%
我们要做到50分,就首先要得到一个结论:我们先将h从小到大排序,那么我们选择的序列一定是连续的一段。
由t>0和n>=1,那么后者的方差一定是大于等于前者的,这个存在传递性,所以得证。
于是就可以枚举开头,枚举长度,然后算一下就好了。
100%
对于100分的算法,我们需要另外一个结论:任意一个序列,它加上一个新元素不会使得其方差更优。
证明与上面类似我就不说了。
然后就是说我们只用求长度为L的序列中的方差的最大值,于是直接扫一遍计算就好了。
当然,上面是出题人的题解,有点麻烦,我自己来搞一个。
拆一波方差公式
然后这个公式就可以维护前缀和了。
于是,从1开始枚举长度为L的子序列即可(当然要以前面题解给出的两个结论为基础)。O(1)询问跑的飞快。
#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<vector>
#include<map>
#define ll long long
#define re register
#define il inline
#define fp(i,a,b) for(re int i=a;i<=b;i++)
#define fq(i,a,b) for(re int i=a;i>=b;i--)
using namespace std;
const int MAX=100005;
int n,L,R;
double h[MAX],sum[MAX],tot[MAX];
double ans=2147483646.0;
void calc(int l,int r)
{
double res;
res=(tot[r]-tot[l-1])/(L*1.0)-((sum[r]-sum[l-1])/(L*1.0))*((sum[r]-sum[l-1])/(L*1.0));//就是方差公式拆完的结果
ans=min(ans,res);
}
void solve()
{
fp(i,L,n)//从1开始枚举长度为L的子序列
calc(i-L+1,i);
}
il int gi()
{
re int x=0;
re short int t=1;
re char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if(ch=='-') t=-1,ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
return x*t;
}
int main()
{
freopen("army.in","r",stdin);
freopen("army.out","w",stdout);
n=gi();L=gi();R=gi();
fp(i,1,n) h[i]=gi();
sort(h+1,h+1+n);
fp(i,1,n) sum[i]=sum[i-1]+h[i],tot[i]=tot[i-1]+h[i]*h[i];//sum存ai的前缀和,tot存ai^2的前缀和
solve();
printf("%.3lf\n",ans);
fclose(stdin);
fclose(stdout);
return 0;
}