【堆模擬費用流增廣】UOJ455 [UER #8] 雪災與外賣
【題目】
原題地址
一條直線上有
個送餐員和
個餐廳。每個送餐員都要去餐廳取菜,花費為距離+菜的價值。每個餐廳有提供菜數量限制,求最小花費。
,其他數字
【解題思路】
題解看這
設送餐員為
,餐廳為
,座標為
。
首先我們可以觀察到一個十分
的性質:
於是我們按順序
,狀態為前
個送餐員匹配到前
個餐廳的最小花費,轉移時列舉第
個餐廳匹配多少個送餐員,用單調佇列優化可以做到
考慮一個更顯然的問題,這個就是一個二分圖匹配問題,我們建圖跑費用流就可以了。
然而這樣做並不優秀,於是我們考慮套路,用堆或線段樹來模擬費用流的增廣,這裡顯然是用堆。
我們考慮把餐廳和送餐員放在一起按照座標排序,從前往後考慮,當考慮到一名送餐員 時,我們先讓它與前面的一個空的餐廳 匹配。費用為 ,那麼我們就是要找一個最小的 。(如果沒有空的餐廳我們可以視為在 處有無窮個餐館)。
匹配完之後,我們可能進行調整,把這名送餐員改為匹配後面的餐廳。那麼我們就可以撤銷這次操作,然後把這名送餐員當做沒有使用過,那麼往堆中丟入 ,即減去之前這組匹配的貢獻,然後尋找新匹配。 掃到餐廳時也類似,先可以匹配一名送餐員,然後可以撤銷這組匹配,尋找新匹配。
對所有餐廳和送餐員分別建一個堆來維護這個操作。注意我們既可以對送餐員進行反悔,也可以對餐廳進行反悔,即每新增一組匹配,我們往兩個堆中分別加入反悔操作。
設每個餐廳能提供
的菜,複雜度是O
這個就是一個純模擬費用流了。
考慮這個過程中我們維護了兩個堆,一個維護待匹配的送餐員,一個維護待匹配的餐廳。之前複雜度不對的原因是送餐員堆的複雜度沒有保證,因為一個餐廳匹配了一名送餐員之後,這名送餐員可能反悔,所以這名送餐員又會重新丟入送餐員堆中。
但注意到對於送餐員 和餐廳 來說,若 ,那麼當送餐員 和 在匹配了餐館 時,丟到堆裡的元素都是 ,也就是說每次餐館匹配了若干送餐員後,往送餐員堆裡面丟的東西都是等權的,因此只需要丟一次就行了。
複雜度就是 的了。
【參考程式碼】
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+10,INF=0x3f3f3f3f;
int n,m,cnt;
ll ans,tot;
int read()
{
int ret=0;char c=getchar();
while(!isdigit(c)) c=getchar();
while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
return ret;
}
struct node
{
int x,w,c;
node(int _x=0,int _w=0,int _c=0):x(_x),w(_w),c(_c){}
bool operator <(const node&a)const{return x<a.x;}
}a[N];
struct heap
{
ll v;int t;
heap(ll _v=0,int _t=0):v(_v),t(_t){}
bool operator <(const heap&a)const{return v==a.v?t<a.t:v>a.v;}
};
priority_queue<heap>q1,q2;
int main()
{
#ifndef ONLINE_JUDGE
freopen("UOJ455.in","r",stdin);
freopen("UOJ455.out","w",stdout);
#endif
n=read();m=read();cnt=n;
for(int i=1;i<=n;++i) a[i].x=read();
for(int i=1;i<=m;++i)
{
int x=read(),w=read(),c=min(read(),n);tot+=c;
if(c) a[++cnt]=node(x,w,c);
}
if(tot<n){puts("-1");return 0;}
a[++cnt]=node(-INF,INF,N);
sort(a+1,a+cnt+1);
for(int i=1;i<=cnt;++i)
{