题目大意:有n个数,希望有连续k个数大小相等,一次操作可以给一个数+或-1,求最少操作次数
题解:考虑枚举每个长度为k的区间,只要有一个 log 级别的方法计算一个区间的答案,问题就解决了。容易发现区间的数都要统一为中位数,就是直线上很多点,选一个到所有点距离和最小的位置,证明类似蓝书某例题。这个可以用平衡树,权值树状数组/线段树,主席树,对顶堆维护
我的收获:计算big和small重新理解了一下实现原理
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int M=100005;
#define son(x,y) c[c[x][y]][y]
int n,top,x,l,r,pos,q;
int c[M][2],sz[M],a[M];
long long sum[M],small,big,k[M];
namespace SBT{
inline void pushup(int x){sz[x]=sz[c[x][0]]+sz[c[x][1]]+1;sum[x]=sum[c[x][0]]+sum[c[x][1]]+k[x];}
inline void node(int &x,int v){x=++top,c[x][0]=c[x][1]=0,sz[x]=1,k[x]=sum[x]=v;}
void rotate(int &x,int k)
{
int y=c[x][k^1];
c[x][k^1]=c[y][k];
c[y][k]=x;pushup(y);
pushup(x);x=y;
}
void insert(int &x,int v)
{
if(!x) node(x,v);
else
{
bool m=v>=k[x];
insert(c[x][m],v);
if(sz[son(x,m)]>sz[c[x][m^1]])
rotate(x,m^1);
}
pushup(x);
}
int select(int &x,int w)
{
int r=sz[c[x][0]]+1;
if(w<r) {big+=k[x]+sum[c[x][1]];return select(c[x][0],w);}//往左子树找,big加上自己和右子树和
else if(w>r) {small+=k[x]+sum[c[x][0]];return select(c[x][1],w-r);}//同上
else {small+=sum[c[x][0]];big+=sum[c[x][1]]; return k[x];}//记得处理一下
}
void del(int &x,int v){
if(!x) return ;sz[x]--,sum[x]-=v;
if(k[x]!=v) del(c[x][v>k[x]],v);
else{
int l=c[x][0],r=c[x][1];
if(l*r==0) x=l+r;
else {
while(c[r][0]) r=c[r][0];
k[x]=k[r];
del(c[x][1],k[x]);
}
}
}
void calc(long long &ans){
small=big=0;
long long tmp=select(x,pos);
ans=min(ans,(tmp*(pos-1)-small+big-tmp*(q-pos)));//减去小的,加上大的
}
}
void work()
{
long long ans=1ll<<60;
for(int i=1;i<q;i++) SBT::insert(x,a[i]);
for(r=q;r<=n;r++)
SBT::insert(x,a[r]),SBT::calc(ans),SBT::del(x,a[++l]);
cout<<ans<<endl;
}
void init()
{
cin>>n>>q;
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
pos=(q+1)>>1;//蓝书上证明了:对于偶数区间长度,设中间两个数为l,r,则[l,r]区间内均可
}
int main()
{
init();
work();
return 0;
}