题意简述
给出一个 n(n≤105) 个数的序列 a(max{a}≤106) ,每次给一个数+1/-1。求使得序列中存在连续 k(k≤n) 个相等的数至少要操作几次。
分析
题目实际上求的是
|x1−h|+|x2−h|+...+|xk−h|
的最小值,其中
x
是
易知
证明
记序列
x 中小于 h 的有c1 个,大于 h 的有c2 个。
因为当 h 增大Δh ,原式就增大 Δh×c1−Δh×c2 ;
所以当 c1<c2 时, h 越大原式越小;c1>c2 时, h 越大原式越大。
c1=c2 时原式取得最小值,此时 h 为序列x 的中位数。因为 max{a}≤106 可以开数组,我们用树状数组实现。
维护 x 中小于等于i 的数的个数 cnt[i] ,小于等于 i 的数的和sum[i] 。首先通过二分找出中位数 h0 ,则原式的最小值为h0×cnt[i]−sum[i]+h0×(k−cnt[i])×(Σxi−sum[i])
总时间复杂度约为 O(nlog2max{a}) 。实现
开两个树状数组分别维护 cnt[i],sum[i] 。
代码
//[POI2008]鐮栧潡Klo #include <cstdio> #include <algorithm> using namespace std; typedef long long lint; int const N=1e5+10; int const M=1e6+10; lint const INF=1LL<<62; int n,k,a[N]; int maxH; lint s[N]; lint tr[M],trs[M]; void add(int x,int f) { int x1=x; while(x1<=maxH) tr[x1]+=f,x1+=x1&(-x1); x1=x; while(x1<=maxH) trs[x1]+=f*x,x1+=x1&(-x1); } lint sum1(int x) { lint res=0; while(x>0) res+=tr[x],x-=x&(-x); return res; } lint sum2(int x) { lint res=0; while(x>0) res+=trs[x],x-=x&(-x); return res; } lint sol(int fr) { int L=0,R=maxH; while(L<R) { int mid=(L+R)>>1; if(sum1(mid)<(k+1)/2) L=mid+1; else R=mid; } lint h=L,c1=sum1(h),c2=k-c1; lint s1=sum2(h),s2=s[fr+k-1]-s[fr-1]-s1; return (c1*h-s1)+(s2-c2*h); } int main() { scanf("%d%d",&n,&k); for(int i=1;i<=n;i++) scanf("%d",&a[i]),a[i]++; for(int i=1;i<=n;i++) maxH=max(maxH,a[i]); for(int i=1;i<=n;i++) s[i]=s[i-1]+a[i]; lint ans=INF; for(int i=1;i<=k;i++) add(a[i],1); ans=min(ans,sol(1)); for(int i=2;i<=n-k+1;i++) { add(a[i-1],-1); add(a[i+k-1],1); ans=min(ans,sol(i)); } printf("%lld",ans); return 0; }
注意
要开long long
原数列中可能有0,先给全体+1s+1