Flow
Astrayt
點一下角色會有驚喜喔
課程簡介
課程內容
- 什麼是Flow?
- 最大流
- 割與MCMF其一
- 可愛的題目
- MCMF其二
- 更多可愛的題目
資源包
服用指示
什麼是Flow
差不多長右邊那樣
總之就是在圖上流來流去
直到走到終點
正式一點的講法
在一張帶有非負權重的有向圖 \(G(V,E)\) 中
(若\((u,v) \notin E\),則權重\(cap(u,v)=0\))
而權重代表是此邊的流量限制
再來圖上有兩個特殊點:源點\(s,匯點t\)
現在我們假定一個函數\(f(u,v)\)為流過\(u,v\)兩點的流量,則此函數必定滿足:
-
流量限制:\(f(u,v) \leq cap(u,v)\)
-
流量對稱:\(f(u,v) = -f(v,u)\)
-
流量守恆:\(\sum_{v \in V} f(u, v) = 0 \),且 \(u \in V \backslash \{ s , t \} \)
示範
從CP-Algorithm那邊偷來的
示範
從CP-Algorithm那邊偷來的
一些名詞解釋
剩餘網路
這張圖還能怎麼流
特別感謝FHVirus讓我偷圖
增廣路徑
還能讓Flow變更大的簡單路徑
Maximum Flow
先來個例題
就是要試著把終點塞滿嘛
可是怎麼做?
我們先想一下
怎麼找到一個從\(s到t\)的路徑
我們先想一下
怎麼找到一個從\(s到t\)的路徑
沒錯,就是BFS!
只要剩餘網路還能走到\(t\)
我們就能找到增廣路徑了
路徑上點\(cap(u,v)\)的最小值即是這次增加的大小
所以只要不斷BFS,搜到沒有辦法再找為止
就找到最大流了
還能更好!
剛剛的時間複雜度是\(O(VE^2)\)
如果我們直接紀錄每個節點到\(s\)的距離
就能直接把剩餘網路上的增廣路徑通通給抓出來
最多增廣\(V次,然後抓增廣最多O(VE)\)
複雜度\(O(V^2E)\)
於是我們得到了Dinic
實作如下:
struct Dinic{
int n, s, t;
vector<int> level, iter;
struct edge{
int to, cap, rev;
edge(int v, int capacity, int i){
to = v; cap = capacity; rev = i;
}
};
vector<vector<edge>> adj;
void init(int _n, int _s, int _t){
s = _s; t = _t;
n = _n; adj.assign(n + 1, vector<edge>());
}
void add_edge(int from, int to, int cap){
adj[from].pb(edge(to, cap, adj[to].size()));
adj[to].pb(edge(from, 0, adj[from].size() - 1));
}
void do_bfs(){
level.assign(n + 1, 0);
queue<int> bfs; bfs.push(s); level[s] = 1;
while(bfs.size()){
int u = bfs.front(); bfs.pop();
for(auto [v, c, r]:adj[u]){
if(c && !level[v]){
bfs.push(v);
level[v] = level[u] + 1;
if(v == t) return;
}
}
}
}
int dfs(int u, int f){
if(u == t) return f;
//next line is an important time saving detail!
for(int &i = iter[u]; i < adj[u].size(); ++i){
auto &[v, c, r] = adj[u][i];
if(level[v] != level[u] + 1 || c <= 0) continue;
int ret = dfs(v, min(f, c));
if(ret <= 0) continue;
c -= ret;
adj[v][r].cap += ret;
return ret;
}
return 0;
}
int flow(){
do_bfs();
int maxflow = 0, f;
while(level[t]) {
iter.assign(n + 1, 0);
while(f = dfs(s, inf)) maxflow += f;
do_bfs();
}
return maxflow;
}
}dinic;
其實還有其他的喔喔喔
像是Ford-Fulkerson或其他的
但會Dinic就好了
而且講師太爛不會更好的
備課時的小插曲
我在查資料之前一直以為我寫的東西是Dinic
結果他是Edmonds-Karp
最小割
MCMF其一
🈹
總之就是把點分成兩群
一群\(s\)一群\(t\)
🈹
總之就是把點分成兩群
一群\(s\)一群\(t\)
那最小割怎麼做呢?
就看maxflow裡面哪邊卡住了
\(若邊E(u,v)存在且不是反向邊,而cap(u,v)最後是0\)
\(則u屬於s,v屬於t\)
🈹
\(Cut(S,T) = \sum_{u \in S} \sum_{v \in T} cap(u, v) \)
🈹
那其實只要做完maxflow就可以知道哪邊是S哪邊是T了嘛
那接下來就是要介紹Min-Cut Max-Flow Theroem了
畢竟我們可以很感性地發現最小🈹就是最大流
不嚴謹的證明時間
觀察1:
最大流 \(\leq\) 最小割
\(\sum_{u \in S} \sum_{v \in T} flow(u, v) \leq \sum_{u \in S} \sum_{v \in T} cap(u, v)\)
就流出去的量最多只能和限制相等
觀察2:
現在是最大流 \(\Leftrightarrow\) 沒有增廣路徑
你如果能增廣就不是最大了嘛
不嚴謹的證明時間
構造一下:
在剩餘網路上可以流到的點在\(S\)
不能的在\(T\)
發現對於所有\(u \in S, v \in T,f(u,v) = cap(u,v)且f(v,u) = 0\)
很顯而易見的,不滿足就有增廣路徑
所以他是最大流
不嚴謹的證明時間
構造一下:
而因為發現所有的\(u \in S, v \in T,f(u,v) = cap(u,v)\)
代表他已經被塞滿了!
代表\(|f| = |Cut(S,T)|\)
又因為\(|f| \leq 任何的割\)
所以可以知道這樣的\(S,T\)是最小割且最大流\(=\)最小割!
休息一下吧
來寫可愛的題目們
TIOJ 2037 & TIOJ 2134 & CSES School Dance (三題差不多)
我寫過的題目有點少
其實重點不在演算法上,在怎麼把問題轉成max-flow
或是轉換成min-cut的樣子
要講解嗎?
休息完再講
MCMF其二
最小費用流
最小費用流
要找到最小費用的路徑
是不是好像有點熟悉?
想想最短路徑!
既然費用可能是負的,Dijkstra顯然不可用
那只要一直做SPFA就好了
最小費用流
題目說要找 \(K\) 條最短的路徑
那我們最多做 \(K\) 次SPFA
就可以知道答案了
時間複雜度 \(O(NMK)\)
Code:
#include <bits/stdc++.h>
using namespace std;
#define starburst ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
typedef long long ll;
#define int ll
#define vi vector<int>
#define pb push_back
#define inf 1e18
#define skip(x) if(x)continue;
struct MCF{
int n, s, t;
int cost[505], p[505], ep[505];
bool vis[505], inq[505];
struct edge{
int v, ca, co, rev;
edge(int _v, int _ca, int _co, int _rev): v(_v), ca(_ca), co(_co), rev(_rev){}
};
vector<edge> adj[505];
void init(int _n){
n = _n; s = 1; t = n;
for(int i = 1; i <= n; ++i){
cost[i] = 1e9; inq[i] = vis[i] = p[i] = ep[i] = 0;
}
}
void addedge(int u, int v, int ca, int co){
adj[u].pb(edge(v, ca, co, (int)adj[v].size()));
adj[v].pb(edge(u, 0, -co, (int)adj[u].size() - 1));
}
int flow(){
init(n);
cost[s] = 0;
queue<int> bfs; bfs.push(s);
int flow = inf;
while(bfs.size()){
int u = bfs.front(); bfs.pop();
inq[u] = 0; vis[u] = 1;
for(int i = 0; i < adj[u].size(); ++i){
edge e = adj[u][i]; int v = e.v;
if(e.ca != 0 && cost[u] + e.co < cost[v]){
p[v] = u;
ep[v] = i;
cost[v] = cost[u] + e.co;
if(!inq[v]){
inq[v] = 1;
bfs.push(v);
}
}
}
}
if(vis[t] == 0) return 0;
for(int u = t; u != s; u = p[u]){
flow = min(flow, adj[p[u]][ep[u]].ca);
}
for(int u = t; u != s; u = p[u]){
adj[p[u]][ep[u]].ca -= flow;
adj[u][adj[p[u]][ep[u]].rev].ca += flow;
}
return flow;
}
}mcmf;
void solve(){
int n, m, k, ans = 0; cin >> n >> m >> k;
mcmf.init(n);
for(int i = 1; i <= m; ++i){
int u, v, cap, cost;
cin >> u >> v >> cap >> cost;
mcmf.addedge(u, v, cap, cost);
}
while(k > 0){
int flow = mcmf.flow();
if(mcmf.vis[n] == 0) {
ans = -1;
break;
}
ans += min(flow, k) * mcmf.cost[n];
k -= flow;
}
cout << ans;
}
signed main(){
starburst
int t = 1; //cin >> t;
while(t--) solve();
}
好像沒什麼能講的了
我弱,我只備到這
大家可以去找題目寫
更多可愛的題目
Flow
By ck1090329王民人
Flow
我好笨喔
- 355