BZOJ1112 - [POI2008]砖块Klo
题意简述
给出一个
分析
题目实际上求的是
易知
证明
记序列
x 中小于h 的有c1 个,大于h 的有c2 个。
因为当h 增大Δh ,原式就增大Δh×c1−Δh×c2 ;
所以当c1<c2 时,h 越大原式越小;c1>c2 时,h 越大原式越大。
c1=c2 时原式取得最小值,此时h 为序列x 的中位数。
因为
维护
总时间复杂度约为
实现
开两个树状数组分别维护
代码
//[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