1112: [POI2008]砖块Klo

题目链接

题目大意:有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;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值