1. 程式人生 > >BZOJ1112 - [POI2008]磚塊Klo

BZOJ1112 - [POI2008]磚塊Klo

spa time clas print ret main 實現 數組 while

原題鏈接

題意簡述

給出一個\(n(n \leq 10^5)\)個數的序列\(a(max\{a\}\leq10^6)\),每次給一個數+1/-1。求使得序列中存在連續\(k(k \leq n)\)個相等的數至少要操作幾次。

分析

題目實際上求的是\(|x_1-h|+|x_2-h|+...+|x_k-h|\)的最小值,其中\(x\)\(a\)的一個長度為\(k\)的子串。
易知\(h\)為序列\(x\)的中位數時原式取得最小值,那我們的任務就是求區間中位數咯。

證明

記序列\(x\)中小於\(h\)的有\(c_1\)個,大於\(h\)的有\(c_2\)個。
因為當\(h\)增大\(\Delta h\)

,原式就增大\(\Delta h \times c_1 - \Delta h \times c_2\)
所以當\(c_1<c_2\)時,\(h\)越大原式越小;\(c_1>c_2\)時,\(h\)越大原式越大。
\(c_1=c_2\)時原式取得最小值,此時\(h\)為序列\(x\)的中位數。

因為\(max\{a\}\leq10^6\)可以開數組,我們用樹狀數組實現。
維護\(x\)中小於等於\(i\)的數的個數\(cnt[i]\),小於等於\(i\)的數的和\(sum[i]\)。首先通過二分找出中位數\(h_0\),則原式的最小值為\[h_0 \times cnt[i]-sum[i] + h_0 \times (k-cnt[i]) \times (\Sigma{x_i}-sum[i])\]


總時間復雜度約為\(O(nlog^2max\{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

BZOJ1112 - [POI2008]磚塊Klo