洛谷 P3466 [POI2008]KLO-Building blocks 支持删除的堆

题面
这题怎么人均 f h q fhq fhq啊,让 S T L STL STL选手情何以堪?

于是就来一发 p r i o r i t y priority priority_ q u e u e queue queue+map的题解吧(突然发现map好慢啊)。

思路上面几位都说得比较清晰了,就是动态维护中位数,然后每次求最小代价,当时我脑袋一热,就想到了对顶堆,就是定义1个小根堆和1个大根堆,每次判断当前的数如果小于大根堆顶,就加入大根堆,否则加入小根堆,这样的话大根堆里的所有的数都比小根堆里的数小了,维护2个堆的大小相差不超过1后,就可以很容易取出中位数了。于是码码码之后发现:

好像要支持删除啊…

不过还有办法,支持搞出删除的堆的套路有2种(pb_ds的走开):

一是把删除的数塞到另一个堆里面,如果2个堆顶相同,就同时删除(注意必须都是小根堆或大根堆)

第二是把数直接放入 m a p map map中,每次取出时要特判

本来本蒟蒻用的是 m a p map map,结果不开 O 2 O_2 O2勇夺最优解倒数第二,实在是不敢看,所以改成了第一种方法。因为对顶堆是一个大根堆一个小根堆,所以删除的堆也要2个。

然后是计算代价的部分了,我们因为是计算绝对值,所以我们是不能把区间的数加起来再减去中位数乘区间数的。但是也好办,我们只要统计大根堆中的数的和 s u m 1 sum_1 sum1,数的个数 s i z e 1 size_1 size1以及小根堆的数的和 s u m 2 sum_2 sum2,数的个数 s i z e 2 size_2 size2,那么代价为:
C o s t = m i d ∗ s i z e 1 − s u m 1 + s u m 2 − m i d ∗ s i z e 2 Cost=mid*size_1-sum_1+sum_2-mid*size_2 Cost=midsize1sum1+sum2midsize2
原因也很好理解,因为大根堆中的数都小于等于中位数,所以可以一起加减,小根堆的数都大于等于中位数,所以同理。

代码(开 O 2 O_2 O2后跑到了最优解 R a n k 1 Rank1 Rank1):

// luogu-judger-enable-o2
#include<bits/stdc++.h>
using namespace std;
#define int long long
//答案可能会爆int
inline void read(int &x){
    x=0;int fh=1;char c=getchar();
    while(c>'9'||c<'0'){if(c=='-')fh=-1; c=getchar();}
    while('0'<=c&&c<='9'){
        x=x*10+c-48;
        c=getchar();
    }
    x*=fh;
}//快读
inline int max(int a,int b){return a>b?a:b;}
inline int min(int a,int b){return a<b?a:b;}
priority_queue<int>Q1,Q2,D1,D2;
//分别表示大根堆、小根堆、用于删除的2个堆
//因为本蒟蒻记不住pq的格式,就把Q2里的数全部加负号来当成小根堆用,所以下文也会加负号还原
int n,k,a[100005],siz1,siz2,sum1,sum2,ans,Begin,ANS;
//siz1和siz2表示在Q1和Q2中有多少数应被删除但还在堆里
void balance(){
    while(Q1.size()-siz1>Q2.size()-siz2+1){
        if(!D1.empty()&&D1.top()==Q1.top()){D1.pop();Q1.pop();siz1--;continue;}
        sum2+=Q1.top();
        sum1-=Q1.top();
        Q2.push(-Q1.top());
        Q1.pop();
    }
    while(Q1.size()-siz1<Q2.size()-siz2){
        if(!D2.empty()&&D2.top()==Q2.top()){D2.pop();Q2.pop();siz2--;continue;}
        sum1+=-Q2.top();
        sum2-=-Q2.top();
        Q1.push(-Q2.top());
        Q2.pop();
    }

}
//保持2个堆的平衡,为了方便,我们钦定大根堆数量大于等于小根堆数量,那么我们取中位数就可以在大根堆里取了
signed main(){
    ans=1e17;
    read(n);read(k);
    for(int i=1;i<=n;++i)read(a[i]);
    for(int i=1;i<=n;++i){
        if((!Q1.empty()&&a[i]>Q1.top())){Q2.push(-a[i]);sum2+=a[i];}
        else {Q1.push(a[i]);sum1+=a[i];}
        balance();
        if(i>=k){
            int mid=-1;
            while(mid==-1){
                balance();
                if(Q1.size()-siz1>=Q2.size()-siz2){
                    if(!D1.empty()&&D1.top()==Q1.top()){D1.pop();Q1.pop();siz1--;}
                        else mid=Q1.top();
                }
            }
            //找中位数
            if(ans>(Q1.size()-siz1)*mid-sum1+sum2-(Q2.size()-siz2 )*mid){
                ans=(Q1.size()-siz1)*mid-sum1+sum2-(Q2.size()-siz2 )*mid;
                Begin=i-k+1;
                ANS=mid;
            }
            //求最小代价
            if(Q1.top()>=a[i-k+1]){
                siz1++;
                D1.push(a[i-k+1]);
                sum1-=a[i-k+1];
            }
            else{
                siz2++;
                D2.push(-a[i-k+1]);
                sum2-=a[i-k+1];
            }
            //删除
        }
    }
    printf("%lld\n",ans);
    for(int i=1;i<=n;++i)if(i>=Begin&&i<Begin+k)printf("%lld\n",ANS);
    else printf("%lld\n",a[i]);
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值