在本蒟蒻學習的過程中參考了這位和這位dalao的部落格
費用流,是oi中解決最優化最優化問題的乙個常用演算法。但眾所周知費用流的模型雖然很容易構建,但他的時間效率卻比較低下
模擬費用流方法是指利用除費用流以外的手段解決一些費用流問題。一般來說,乙個問題如果使用模擬費用流演算法來解決,你在整個**中不會見到任何乙個與費用流有關的片段。可以說,這個方法非常的抽象
而在這乙個演算法的學習過程中,也存在著一些比較實用的模型。通過對這些模型的分析讓我們可以對模擬費用流的理解步步加深
你現在正在處理乙個問題:
在一條數軸上有一些兔子和一些洞,其中兔子的座標為\(x_i\),洞的座標為\(y_i\)。兔子只能向左走,現在你要最小化所有兔子行走的距離之和。對於這個問題,很顯然我們按座標排序後從左到右操作,每次遇到兔子的時候考慮找到它前面的最近的未匹配的洞
用乙個棧就可以維護上面的操作
在之前的基礎上,洞有了附加權值\(v_i\),這意味著如果要選擇這個洞那麼必須多付出\(v_i\)的代價(相當於開啟洞門?)。還是求最小的代價之和。還是和之前一樣,從左往右考慮每個兔子和洞
如果當前這個位置是乙個兔子\(a\),那麼讓它進入之前的乙個洞\(b\)的代價就是\(x_a-y_b+v_b\),這也就意味著我們要取\(-y_b+v_b\)最小的洞,可以用乙個堆來儲存所有的洞
但我們發現這個貪心的想法只侷限於眼下,可能最優的解是另乙個兔子\(c\)來和洞\(b\)匹配,然後\(a\)不匹配。那麼我們考慮去掉\(a\)對答案的影響,那麼就是要減去它的影響\(x_a\)
那麼怎麼處理呢,很簡單,我們選擇了\(a\)之後就向堆裡扔進去乙個權值為\(-x_a\)的洞。這樣選擇了這個洞就意味這進行了反悔操作
注意這裡我們取消\(a,b\)的匹配,改為\(c,b\)的匹配,這個操作是不是很像費用流的退流操作,因此實際上模擬費用流的本質就是用其他的東西來模擬退流,也就是說我們要考慮怎麼在貪心的基礎上實現反悔
好了知道了這個模型之後你就可以去做一道板題了:bzoj 4977 跳傘求生,就是把這裡的最小值改成最大值,維護的方法完全類似
#include#include#include#define ri register int
#define ci const int&
using namespace std;
const int n=100005;
struct data
else hp.push(a[i].val-a[i].pos);
return printf("%lld",ans),0;
}
在之前(或之後)的任意一種模型中,每個兔子必須找到洞來匹配。考慮在開始時就讓每乙個兔子都與乙個距離它\(\infty\)的洞匹配,這樣這個匹配就無法退流
兔子洞有額外權值\(s_i\)。兔子可以向左右兩邊走。注意,這是所有模型中可以說是最重要的乙個了。下面的模型都和它密切相關,一定要掌握透徹
還是和上面的分析類似,我們仍舊是從左向右考慮這個過程,並且還要考慮兔子和後面的洞匹配所帶來的一系列情況
設\(v_i\)表示當兔子找到洞\(i\)時需要付出的額外代價(可以看做維護洞的堆的堆頂),同理設\(w_i\)表示洞選擇兔子\(i\)付出的額外代價
然後,我們發現尤其是在第二種情況中,這種\(c\)搶走了\(b\),\(a\)就去匹配它本來匹配過的\(d\),然後現在匹配\(d\)的點又去……的操作,是不是就是費用流的推流操作(前面的只退一次,這裡就更複雜了)
這時,兔子已經不在是兔子,洞也已經不再是洞了。洞和兔子因為彼此的利益關係交織在一起,但是無論如何,我們只需要乙個堆就可以把它們處理地服服帖帖的
因此,關鍵的關鍵還是通過新增了一些「洞」 和 「兔子」 完成了兩者的 「反悔」 操作
我們在以上的任意模型中,兔子和洞都有分身(即乙個位置上會有很多個兔子和洞),記為\(c_i,d_i\)。求最小的代價和我們可以把分身看做有許多個單獨的情況。但是當分身的數目很多(到\(10^9\)級別)的時候就不能這麼搞了
考慮直接把相同的兔子和洞合併在一起,用乙個pair
綁起來然後用上面的方法做即可
什麼?你說合併的時候產生的新兔子和新洞的數目可能會很多?這個貌似可以用匹配不交叉來證,但是我太弱了不會。不過你只要知道新新增的點都是常數級別的就好了
那麼接下來就可以去做一道題了:uoj#455 雪災與外賣
#include#include#include#include#define ri register int
#define ci const int&
using namespace std;
typedef long long ll;
const int n=100005;
const ll inf=1e12;
struct event
}; priority_queue a,b; long long sum,ans; //a-holes b-rabbits
int main()
if (cs) b.push(data(-a[i].pos-a[i].val,cs));
if (left) a.push(data(a[i].val-a[i].pos,left));
} return printf("%lld",ans),0;
}
在模型四的基礎上,分身必須匹配至少乙個我們把分身拆成兩種,一種有乙個,匹配了可以產生額外價值\(-\infty\),另一種有\(c_i-1\)個,匹配後產生額外價值\(0\)
把兔子和兔子洞搬到樹上。兩點間距離定義為樹上距離。還是考慮先定下一種方向,那麼我們從底向上匹配,每次合併乙個點不同子樹的東西,把堆改為可並堆即可
然後又是一道題:loj#6405 征服世界
#include#include#include#define ri register int
#define ci const int&
#define cl const ll&
#define mp make_pair
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef pair pi;
const int n=250005;
const ll inf=1e12;
struct edge
e[n<<1]; int n,head[n],u,v,c,x[n],y[n],cnt; ll ans,dis[n];
class lefty_tree
node[n*20]; int rt[n*20],tot;
#define lc(x) node[x].ch[0]
#define rc(x) node[x].ch[1]
#define d(x) node[x].dis
#define v(x) node[x].v
inline int merge(int x,int y)
public:
inline void insert(ci pos,cl val,ci num)
inline bool non_empty(ci x)
inline void union(ci x,ci y)
inline pi top(ci x)
inline void remove(ci x)
inline void updata(ci x,ci y)
#undef lc
#undef rc
#undef d
#undef v
}x,y; int totx,toty;
inline void addedge(ci x,ci y,ci z)
; head[x]=cnt;
e[++cnt]=(edge); head[y]=cnt;
}inline void union(ci x,ci y,cl d)
}#define to e[i].to
inline void dfs(ci now=1,ci fa=0)
}#undef to
int main()
{ //freopen("code.in","r",stdin); freopen("code.out","w",stdout);
ri i; for (scanf("%d",&n),i=1;i這就完了?其實才剛剛開始呢……
NOI2019 序列(模擬費用流)
有兩個長度為n的序列,要求從每個序列中選k個,並且滿足至少有l個位置都被選,問總和最大是多少。1 leq l leq k leq n leq 2 10 5 首先,記錄當前考慮到的位置i,第乙個選的數量a,第二個選的數量b,都被選的數量c,可以做到 o n 4 卡常後能過 n leq 150 有40分...
貪心(模擬費用流) NOIP2011 觀光公交
風景迷人的小城y 市,擁有n 個美麗的景點。由於慕名而來的遊客越來越多,y 市特意安排了一輛觀光公交車,為遊客提供更便捷的交通服務。觀光公交車在第0 分鐘出現在1號景點,隨後依次前往2 3 4 n 號景點。從第i 號景點開到第i 1 號景點需要di 分鐘。任意時刻,公交車只能往前開,或在景點處等待。...
NOI2019 序列(模擬費用流)
有兩個長度為n的序列,要求從每個序列中選k個,並且滿足至少有l個位置都被選,問總和最大是多少。1 leq l leq k leq n leq 2 10 5 首先,記錄當前考慮到的位置i,第乙個選的數量a,第二個選的數量b,都被選的數量c,可以做到 o n 4 卡常後能過 n leq 150 有40分...