坑爹的BZOJ因为不让 输出方案我WA了若干次
据说正解是平衡树/线段树,我不会,就用了个新东西—>对顶堆
对顶堆
一个维护中位数的东西,我们通过维护两个堆,来维所有数的中位数
(显然我们可以排个序)
具体实现:
我们维护一个大根堆,把所有小于等于中位数的数放进去,堆顶就是中位数,你会问,为什么会是中位数?我们保证这个堆中的元素个数始终为(n+1)/2个,两个堆元素个数一共为n所以这个堆的堆顶就是中位数,另一个堆是小根堆,维护的所有大于中位数的数放进去,中位数在填完最后一个数之前把那个不是固定的,所以我们要等到所有数加完后,取第一个堆的堆顶元素就好了
具体操作:
先考虑 整个数列 ,不带删除的情况,每加入一个元素,我们先判断放小数的堆是否够了(k+1)/2个,不够就要先放进去,然后够了之后,每次加入一个元素 ,判断和大根堆堆顶的关系,比他大说明比目前的中位数要大,所以放到小根堆中,小的话就放到大根堆中,然后我们发现这时大根堆的元素多了,那么我们就要把大根堆的堆顶放到小根堆中,这样仍然满足性质
然后是部分区间的中位数,就是我们这道题
不难发现 ,把一段连续区间修改为他们的中位数是最优的,所以我们就可以维护每个长度为k的区间修改为中位数的花费,区间移动就涉及到了删除元素的操作 ,但是我们又找不到上一个元素具体在什么位置,怎么办?我们不再用堆的size表示堆中元素个数,我们用两个变量siz1,siz2表示两个堆的大小,然后我们发现,只要删除元素不在堆顶,他在什么位置是没有关系的,所以我们只需呀给他打上标记,然后等他到堆顶时,直接pop掉 就好了,打标记 的同时也要给它所在堆的siz–,判断 所在堆的方法是比较删除元素于大根堆堆顶的大小关系,也就是和在中位数的大小关系
然后这时你可能会说,我们维护了中位数,怎么统计答案呢?我们将求花费的式子列出来并化简得到ans=
+
,然后发现它等于比mid小的数的个数
mid-比mid小的数的和+比mid大的数的和-mid
比mid大的数的个数,个数很好说就是两个堆的大小,和呢?我们 也维护两个值sum1和sum2,表示两个堆的大小,插入和删除时,一块修改就好了
代码
//By AcerMo
#include<cmath>
#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define t1 q1.top()
#define t2 q2.top()
#define grtr greater
#define lli long long int
using namespace std;
const int M=200500;
int n,m;
int a[M],vis[M*10];
lli si1,si2,su1,su2;
priority_queue<int,vector<int>,less<int> >q1;//大根堆,保证堆顶是所有小数的最大值
priority_queue<int,vector<int>,grtr<int> >q2;//小根堆,保证堆顶是所有大数的最小值
inline int read()
{
int x=0;char ch=getchar();
while (ch>'9'||ch<'0') ch=getchar();
while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
return x;
}
inline void write(lli x)
{
if (x>9) write(x/10);
putchar(x%10+'0');
return ;
}
inline void update()
{
if (q1.size())
while (vis[t1]&&q1.size()) vis[t1]--,q1.pop();
if (q2.size())
while (vis[t2]&&q2.size()) vis[t2]--,q2.pop();
return ;
}//清除堆顶的删除元素
inline void add(int x)
{
update();
while (si1<(m+1)/2&&si2)
{
int y=t2;q2.pop();q1.push(y);
si1++;si2--;su2-=y;su1+=y;
update();
}//将大根堆的元素加到中位数的位置上(m+1)/2中位数的位置
if (si1==0)
{
si1++;su1+=x;
return (void)(q1.push(x));
}//这个情况只有在两个堆都是空的的时候会出现,优先放在小数的里
if (x<q1.top()) si1++,su1+=x,q1.push(x);//比大根堆的最大值要小,那么肯定不能放在小根堆里
else si2++,su2+=x,q2.push(x);//不然就放在小根堆里,因为我们已经调整过siz了
update();
while (si1>(m+1)/2&&si1)
{
int y=t1;q1.pop();q2.push(y);
si1--;si2++;su1-=y;su2+=y;
update();
}//然后调整一下大根堆 ,防止多了
update();
while (si1<(m+1)/2&&si2)
{
int y=t2;q2.pop();q1.push(y);
si1++;si2--;su1+=y;su2-=y;
update();
}//也可能是一开始没有调整,然后在这次加入了小根堆,那么我们在先满足大根堆的前提下 ,就把他拿过来就好
return (void)(update());
}
inline void del(int x)
{
vis[x]++;
if (x>q1.top()) si2--,su2-=x;
else si1--,su1-=x;
return (void)(update());
}//删除打个标记就行了
inline lli calc(lli x)
{
lli tot=su2-(lli)m/2*x;
tot+=(lli)(m+1)/2*x-su1;
return tot;
}//计算公式:比中位数小的数的个数*中位数大小-比中位数小的数的和
//+比中位数大的数的和-比中位数大的数的个数*中位数
signed main()
{
n=read();m=read();
for (int i=1;i<=n;i++) a[i]=read();
for (int i=1;i<=m;i++) add(a[i]);
lli mid,l=1,r=m,ans=t1;
lli tot=su2-(lli)m/2*ans;
tot+=(lli)(m+1)/2*ans-su1;
for (int i=m+1;i<=n;i++)
{
del(a[i-m]);add(a[i]);mid=t1;
lli cnt=calc(mid);
if (tot>=cnt&&cnt>=0)
tot=cnt,l=i-m+1,r=i,ans=mid;
}
write(tot);puts("");
/*for (int i=1;i<=n;i++)
if (i>=l&&i<=r) write(ans),puts("");
else write(a[i]),puts("");*/ //BZ这里要删掉
return 0;
}