1. 程式人生 > >[POI2008]磚塊Klo(set維護中位數)

[POI2008]磚塊Klo(set維護中位數)

[POI2008]磚塊Klo

Description

N 柱磚,希望有連續 K 柱的高度是一樣的. 你可以選擇以下兩個動作 1:從某柱磚的頂端拿一塊磚出來,丟掉不要了. 2:從倉庫中拿出一塊磚,放到另一柱.倉庫無限大. 現在希望用最小次數的動作完成任務.

Input

第一行給出 N , K .

( 1 k n 100000 ) , 下面 N
行,每行代表這柱磚的高度. 0 h i 1000000

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);
}