[POI2008]砖块Klo
Description
N N 柱砖,希望有连续柱的高度是一样的. 你可以选择以下两个动作 1:从某柱砖的顶端拿一块砖出来,丢掉不要了. 2:从仓库中拿出一块砖,放到另一柱.仓库无限大. 现在希望用最小次数的动作完成任务.
Input
第一行给出 N N ,. (1≤k≤n≤100000) ( 1 ≤ k ≤ n ≤ 100000 ) , 下面 N N 行,每行代表这柱砖的高度.
Output
最小的动作次数
Sample Input
5 3
3
9
2
3
1
Sample Output
2
解:
看题目发现一个性质,学过初中数学的同学都知道,肯定是把所有数变成中位数最优,所以这道题就变成了动态维护中位数,我们知道了中位数,然后每个作差即可。
如何优雅地维护中位数?
可以使用splay来做,听说权值线段树也可以做。
在网上看到一个简单的维护方法:set,不用手写splay啦!!!
在线维护中位数,比中位数大的放到一个set里,比中位数小的放到另一个set里。如果两个set大小不同就调整一下。这样就很简单地完成啦!
似乎要特判一下n=1.
code(话说set还是挺好写的):
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
using namespace std;
int n,k,a[100005],mid;
long long num1,num2,ans=0x7f7f7f7f7f7f7f;
multiset <int> d1,d2;
multiset <int>::iterator t;
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
mid=a[1];d2.insert(a[1]);num2+=a[1];
for(int i=2;i<=n;i++){
if(i>k){
if(a[i-k]>=mid) d2.erase(d2.find(a[i-k])),num2-=a[i-k];
else d1.erase(d1.find(a[i-k])),num1-=a[i-k];
}
if(a[i]>=mid) d2.insert(a[i]),num2+=a[i];
else d1.insert(a[i]),num1+=a[i];
while(d1.size()+1<d2.size()){
t=d2.begin();
num2-=*t;num1+=*t;
d1.insert(*t);
d2.erase(t);
}
while(d1.size()>d2.size()){
t=d1.end();t--;
num1-=*t;num2+=*t;
d2.insert(*t);
d1.erase(t);
}
mid=*d2.begin();
if(i>=k){
long long r=0;
r+=num2-1ll*d2.size()*mid;
r+=1ll*d1.size()*mid-num1;
ans=min(r,ans);
}
}
if(n==1) ans=0;
printf("%lld",ans);
}