题解:P10389 [蓝桥杯 2024 省 A] 成绩统计

· · 题解

扫一眼题目便知,这是一道我最不擅长的数学题。

仔细观察

\sigma^2=\dfrac {\sum_{i=1}^k(v_i-\bar v)^2} k

便知道,既然要最小化它,那必然要使 \lvert v_i-\bar v \rvert 尽可能小,也就是让每个数离平均值尽量近,也就是每个数尽量接近。那么对于一个有序数列,要选 k 个数使他们最接近,一定会选 [1,k][2,k+1] 这些区间。

然而,我们连数列是什么都不知道,怎么办?二分啊!二分什么呢?二分 x,这样就可以知道数列是谁了。

现在,我们有一种做法,二分 x,再对 1 ∼ x 排序,再遍历区间的结束节点(开始节点也行),再遍历区间,计算方差,判断以所有节点为结束节点的最小方差是否小于 T

然而 O(nk \log(n)) 的时间复杂度只能那 40 分,我们需要优化。很容易想到前缀和,但是 \bar v 会变,怎么办呢?可以把

\sigma^2=\dfrac {\sum_{i=1}^k(v_i-\bar v)^2} k

根据完全平方差公式变成

\sigma^2=\dfrac {k\times \bar v+\sum_{i=1}^k v_i^2- \sum_{i=1}^k 2 \times v_i \times \bar v} {k} $$ \sigma^2=\dfrac {k\times \dfrac {\sum_{i=1}^k v_i} k+\sum_{i=1}^k v_i^2- \sum_{i=1}^k 2 \times v_i \times \dfrac {\sum_{i=1}^k v_i} k} {k} $$ 把 $v_i$ 的前缀和记为 $sum_i$,$v_i^2$ 的前缀和记为 $sum2_i$,那么可以得到方差 $O(1)$ 的计算方法: $$ \sigma^2=\dfrac {sum_k+sum2_k-2 \times sum_k \times \dfrac {sum_k} k} k $$ # 完整代码 ```cpp #include<iostream> #include<algorithm> using namespace std; long long a[100005]; long long b[100005]; long long sum[100005]; long long sum2[100005]; long long n,k,T; bool check(int x){ for(int i=1;i<=x;i++){ b[i]=a[i]; } sort(b+1,b+x+1); for(int i=1;i<=x;i++){ sum[i]=sum[i-1]+b[i]; sum2[i]=sum2[i-1]+b[i]*b[i]; } double ans=1e300; for(int i=k;i<=x;i++){ ans=min(ans,((sum2[i]-sum2[i-k])+ ((1.0*sum[i]-sum[i-k])/k)*((1.0*sum[i]-sum[i-k])/k)*k- 2*(1.0*sum[i]-sum[i-k])*(1.0*sum[i]-sum[i-k])/k)/k); } return ans<T; } int main(){ cin>>n>>k>>T; for(int i=1;i<=n;i++){ cin>>a[i]; } int l=k,r=n,ans=-1; while(l<=r){ int mid=l+(r-l)/2; if(check(mid)){ ans=mid; r=mid-1; } else{ l=mid+1; } } cout<<ans; return 0; } ``` 十年 OI 一场空,不开 `long long` 见祖宗。