P6069 『MdOI R1』Group
一直不会方差的转换,正好遇到了这道题目,好好学了一下。
当年NOIP……哎说多了都是泪。
P6069 『MdOI R1』Group
思路:
1. 二分。
首先,上来我们就可以注意到最终的答案 \(ans\) 是满足可二分性的,那么我们可以考虑二分,现在的问题就是怎么在修改的次数确定的情况下判断是否可行。
2. 考虑怎么修改。
既然我们已经明确可以二分,那么,我们先要考虑在 \(ans\) 确定的时候,我们怎么修改,才能让方差最小。
由于方差的性质我们可知,方差其实就是对于一组数字与他们的相互之间偏差的波动大小,所以我们如果要改变一个数,一定是改变偏差最大的,将它对于波动程度的影响消除,其实就相当于是删除了这个数。
同时我们可以发现最后剩下的数一定是原数组排序后的一段连续的数(可以意会一下),因为最大或最小的数一定比中间的某个数与本数组的偏差大。
在求解的时候,我们可以 \(O(n)\) 枚举长度为 \(n-ans\)(剩下的数的数量)的区间,将这个区间的方差与 \(\frac{m}{n}\) 比较。
那么,我们最后就剩下了一个问题:如何快速求一个区间的方差。
3.方差的转化。
方差的原式子是不太好快速判断的,只能做到 \(O(n)\)判断,这样的话复杂度就是 \(O(n^2\log n)\) 了。
我们考虑将方差的式子转化。
设 \(S\) 为 \(a\) 数组的方差,\(p\) 为平均数。
则:
若设:
之后可转化为:
我们要判断:
即相当于:
这里需要注意修改后的数组相当于删掉了 \(ans\) 个数字,所以在求方差的时候,原数组的长度就不是 \(n\) 了,\(n\) 就要变为 \(n-ans\),设为 \(len\)。
那么我们可以将式子变为:
这样就可以去掉除法导致的精度问题了。
对于 \(psum\) 和 \(sum\) 我们可以前缀和预处理,就可以做到 \(O(1)\) 求方差。
所以最终复杂度就是 \(O(n\log n)\)。
还有,要注意虽然式子右边可以发现一定在 long long 范围内,但是左边的 \(m\times len\) 不一定,所以可以临时强转一下 __int128 或者特判。
Code:
#include<bits/stdc++.h>
using namespace std;
#define ll __int128
inline int read(){
int rt=0; bool kk=0; char g=getchar();
while(g<'0'||g>'9') kk|=(g=='-'),g=getchar();
while(g>='0'&&g<='9') rt=(rt<<3)+(rt<<1)+g-'0',g=getchar();
return (kk?-rt:rt);
}
inline ll lread(){
ll rt=0; bool kk=0; char g=getchar();
while(g<'0'||g>'9') kk|=(g=='-'),g=getchar();
while(g>='0'&&g<='9') rt=(rt<<3)+(rt<<1)+g-'0',g=getchar();
return (kk?-rt:rt);
}
int n,ans;
ll m,ls;
bool kk;
ll a[200005],sum[200005],psum[200005];
int main()
{
n=read(); m=lread();
for(int i=1;i<=n;i++) a[i]=read();
sort(a+1,a+1+n);
for(int i=1;i<=n;i++)
sum[i]=sum[i-1]+a[i],psum[i]=psum[i-1]+a[i]*a[i];
int l=1,r=n,mid;
while(l<=r)
{
mid=(l+r)>>1; kk=0;
for(int i=mid;i<=n&&!kk;i++)
{
ls=sum[i]-sum[i-mid];
kk|=((psum[i]-psum[i-mid])*mid-ls*ls<=m*mid);
}
if(kk) l=mid+1,ans=mid;
else r=mid-1;
}
printf("%d",n-ans);
return 0;
}