1. 程式人生 > >【noip模擬】D(==)

【noip模擬】D(==)

Portal --> who knows ==

Description

  數軸上面有一些洞,有一些老鼠,每個洞有一個容量限制,一隻位於\(x\)的老鼠進到位於\(y\)的洞要花費\(|x-y|\)的代價,問所有老鼠都進洞的最小代價,如果沒有合法方案輸出\(-1\)

  資料範圍:\(n,m<=10^6,1<=c_i<=n\),其中\(c_i\)表示每個洞的容量,\(0<=\)位置\(<=10^9\)

  

Solution

  這題的話。。長得像一個dp。。但是如果直接莽顯然是不行的。。

​  注意到一個點:將洞和老鼠都按照位置排序,最優方案中進入同一個洞的老鼠一定是一段連續的區間,那麼記\(f[i][j]\)

表示前\(i\)個洞,前\(j\)只老鼠已經進洞了的最小代價,轉移的話:
\[ f[i][j]=min(f[i-1][k]+sum[j]-sum[k]) \]
  其中\(k\)的列舉範圍是\([j-c[i],j]\)\(sum[j]\)表示的是\(\sum\limits_{p=1}^j|rat_p-hole_i|\)

​  那麼線段樹優化一下就有一個\(O(nmlogn)\)的做法(然而實際上好像直接單調棧什麼的搞一搞除去排序就是\(O(nm)\)了)

​   

  然後注意到這個dp是沒有前途的。。(沒有辦法繼續優化了),所以換一種思路

  不選擇分開考慮,而是選擇將所有的老鼠和洞放在一起,這裡有一種很妙的用堆的做法(瘋狂orzhwc),用兩個堆分別維護老鼠和洞

  我們將老鼠和洞放在一條線上,一隻老鼠要麼會被丟到前面,要麼會被丟到後面,那麼我們從左往右掃,考慮如果掃到一隻老鼠,我們先無腦將它丟到它前面的離它最近的有容量的洞裡面,更優情況的替換我們放在掃到一個洞的時候處理

  掃到一個洞的時候,如果說有隻當前被丟到前面洞裡的老鼠丟到這個洞裡面會比當前更優,那麼就把這隻老鼠丟進來,更新當前答案,具體的判斷方式是:我們將前面的那個洞的位置以當前老鼠為對稱軸對稱過來,如果說對稱過來得到的位置比當前洞的位置更大,那麼說明當前洞更優

  具體處理的話就是用一個堆維護被丟進前面的洞裡的老鼠對應的洞的對稱值(也就是\(rat+hole\),其中\(rat\)表示這隻老鼠的位置,\(hole\)

表示對應洞的位置),然後如果堆頂的那個老鼠不能被當前這個洞更新,那肯定也不能被後面的洞更新了,所以直接彈掉

  然而這裡有一個問題,當前的決策放在後面不一定是最優的,也就是說有的老鼠當前可能丟到後面比較優,但是放在全域性可能就是丟到前面比較優了,為了應對這種情況,我們需要一個撤銷操作,具體的實現就是,如果說在掃到一個洞的時候,我們用這個洞更新了某隻老鼠,那麼對應的我們要多加一個相當於撤銷操作的洞到洞的堆裡面,撤銷的具體含義就是:如果說當前的老鼠選了一個撤銷洞,那麼就相當於令被撤銷洞更新的老鼠重新回到更新前的洞裡,並且將當前的老鼠丟到這個洞裡

​  實現上的話,假設撤銷洞位置為\(y\),被撤銷洞更新的老鼠\(A\)在更新前在的洞是\(x\)\(delta\)是該老鼠選\(y\)比選\(x\)優多少,那麼撤銷洞的位置就應該為\(y+delta\),並且容量為\(1\),之所以這麼設是因為:如果說當前的老鼠\(B\)選了這個洞,那麼\(ans\)會被加上\(rat_B-(y+delta)\),也就是相當於將\(delta\)去掉(老鼠\(A\)回到\(x\)),然後再加上老鼠\(B\)\(y\)這個洞的貢獻

  然後一路掃過去就好了ovo

  

  mark:撤銷洞的思路很有意思,mark一下

  

  程式碼大概長這個樣子

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#define ll long long
#define Pr pair<ll,int>
#define mp make_pair
using namespace std;
const int N=1000010;
const ll inf=1LL<<60;
struct Data{
    int p,c,ty;
    friend bool operator < (Data x,Data y){
        return x.p==y.p?x.ty<y.ty:x.p<y.p;
    }
}a[N*2];
priority_queue<Pr> hole;
priority_queue<ll> rat;
int n,m,cnt;
ll ans;
void solve_rat(int i){
    ll w=inf;
    Pr tmp;
    if (!hole.empty()){
        tmp=hole.top(); hole.pop();
        w=a[i].p-tmp.first;
        --tmp.second;
        if (tmp.second)
            hole.push(tmp);
    }
    rat.push(w+a[i].p);
    ans+=w;
}
void solve_hole(int i){
    ll delta;
    while (a[i].c&&!rat.empty()&&a[i].p<rat.top()){
        delta=a[i].p-rat.top(); rat.pop();
        ans+=delta;
        --a[i].c;
        hole.push(mp(a[i].p+delta,1));
    }
    if (a[i].c)
        hole.push(mp(a[i].p,a[i].c));
}
void solve(){
    for (int i=1;i<=cnt;++i)
        if (a[i].ty==0)
            solve_rat(i);
        else
            solve_hole(i);
    printf("%lld\n",ans);
}
void print(Pr x){printf("(%d,%d)\n",x.first,x.second);}

int main(){
#ifndef ONLINE_JUDGE
    freopen("a.in","r",stdin);
#endif
    int x,y;
    ll sum=0;
    scanf("%d%d",&n,&m);
    cnt=0;
    for (int i=1;i<=n;++i) scanf("%d",&a[++cnt].p),a[cnt].ty=0;
    for (int i=1;i<=m;++i){
        scanf("%d%d",&x,&y);
        if (!y) continue;
        a[++cnt].p=x; a[cnt].c=y; a[cnt].ty=1;
        sum+=y;
    }
    if (sum<n){printf("-1\n"); return 0;}
    sort(a+1,a+1+cnt);
    solve();
}