1112: [POI2008]砖块Klo

来源:互联网 发布:草莓音乐节 上海 知乎 编辑:程序博客网 时间:2024/04/26 07:24

题目链接

题目大意:有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;}