聽說不難?
在一張有向圖 \(G = (V, E) \)上,
每個邊有一個非負實數的權重 \(c(u, v) \ge 0\) 代表容量,
(若 \((u, v) \notin E\),則 \(c(u, v) = 0\))
並有兩個特殊點: 源點 \(s\) 與匯點 \(t\)。
一個可行流是一個函數 \(f : V \times V \mapsto \mathbb{R}\),
其滿足以下條件:
定義這個網路流的流量為 \(\sum_{v \in V}{f(s, v)}\),
而最大的可行流則稱為最大流。
所以網路流黑盒子做的事情就是:
給定一張有向帶權圖 \(G = (V, E)\) 以及 \(s, t\),
吐出他的最大流。
這邊就當作你有黑盒子了。
沒有也沒關係,反正黑盒子就是黑盒子。
void solve(){
int n, m; cin >> n >> m;
// 設 n 個點, 1 為源點, n 為匯點
din.init(n, 1, n);
for(int u, v, c; m; --m){
cin >> u >> v >> c;
// 增加一條 u 到 v 容量為 c 的邊
din.add(u, v, c);
}
// 我叫你流
cout << din.flow() << '\n';
}
後面才會講黑盒子的實作與複雜度
1
1
1
2
2
6/7
4/4
2/3
3/3
2/3
4/5
6/8
5/5
1/3
剩餘網路 \(G_r\):現在這個網路還可以怎麼流
注意反向邊(紅色)
擴增路徑:一條在 \(G_r\) 上從 \(s\) 到 \(t\) 的簡單路徑
也就是剩餘流量 \(> 0\) 的一條簡單路徑
注意反向邊(紅色)
\(s\)
\(1\)
\(2\)
\(t\)
7/14
7/7
7/49
\(s\)
\(1\)
\(2\)
\(t\)
7/14
7/7
7/49
\(Cut(s, t) = \sum_{u \in s}{\sum_{v \in t}{ c(u, v)}}\)
講師不會數學,所以就先證個大概
反正也沒有人想聽
\(\sum_{u \in S}{\sum_{v \in T}{f(u, v)}} \le \sum_{u \in S}\sum_{v \in T}{c(u, v)}\)
應該蠻好理解的?
送出去的流量 \(\le\) 出邊的流量上限
應該顯然吧
用反證法可知若不滿足這兩點,
則有擴增路徑(\(\rightarrow\leftarrow\))。
不但找到了 \(f\) 使得 \(\vert f \vert = \vert C \vert\),
也得知了 \(C\) 每一條邊都被塞滿滿!
令最大流 \(f\),構造出的割 \(C\)
\(\Rightarrow\) 最小割=最大流,且 \(C\) 為最小割!
現在 INFOR 社辦某一台電腦被 FHVirus 感染了,
社員們要想辦法在 FHVirus 感染到
存放了重要Jizz資料的某一台電腦前阻止他。
FHVirus 只能透過 \(n\) 台電腦的 \(m\) 條網路線散播,
每一條網路線都有一個貴重程度 \(w_i\),
而社員們只能用剪斷網路線來防禦。
由於 FHVirus 散播速度跟FWT一樣快,
時間所剩不多,請求出
「剪斷線路貴重程度和」最小的防禦方式吧!
註:只要守住「存放了重要Jizz資料的某一台電腦」就好
透過改變/構造圖,
使得既有的演算法能在新圖上得到答案。
題目要最短路
\(\Rightarrow\) 如果能 Dijkstra 就好了⋯⋯
\(\Rightarrow\) 建模維護額外資訊!
把每一個點 \(u\) 變成 \(u_0 \sim u_{1023}\),
代表到 \(u_i\) 且 \(xor\) 值為 \(i\) 的最短路。
題目要點容量
\(\Rightarrow\) 如果能換成邊容量就好了⋯⋯
\(\Rightarrow\) 建模,把點變成邊!
對於每一個點 \(u\),拆成 \(u_{in}, u_{out}\),
並建兩點間容量為點容量大小的邊!
每一個紅點要對應到一個藍點,
求最多紅藍出CP點對數
\(\Rightarrow\) 如果能換成最大流量就好了⋯⋯
\(\Rightarrow\) 建模,把匹配變成流量為 \(1\) 的邊!
對於每一個紅點 \(u\),建 \((s, u, 1)\) 的邊;
對於每一個藍點 \(v\),建 \((v, t, 1)\) 的邊;
對原圖上的 \((u, v)\),建 \((u, v, 1)\) 的邊。
這樣最大流就是最大匹配數!
先爆個雷
匹配用 Dinic 的複雜度是 \(O(E \sqrt{V})\),
超級快!跟 ZCK 破台的速度一樣
(用擴增路徑找匹配是 \(O(VE)\))
看到這種東西:
又沒有辦法好好 DP 的時候⋯⋯
每一個東西分成要選或不選兩類⋯⋯
\(\Rightarrow\) 如果能用最小割就好了⋯⋯
\(\Rightarrow\) 建模,把要不要選變成分在 \(S\) 或 \(T\)!
把上面四種關係(實際上是三種)
巧妙地變成邊之後,
最小花費就變成最小割!
假設要選的在 S-cut,不選的在 T-cut:
拿 \(u\) 會賺 \(c\) :
先假裝賺了 \(c\) ,等等決定不選再扣回去!
\(ans = ans + c, cap(s, u) = c\)
還有一大堆酷酷的模沒有講到,
有興趣可以去看 IOIC 講義。
(沒有的話就祈禱明年也能順利辦成吧!)
你又沒有要裝黑幹嘛要黑盒子
假設流量為 \(f\),沿途所有邊 \(c = c - f\),
沿途所有邊的反向邊 \(c = c + f\)。
反向邊的概念很重要。
int fordFulkerson(){
int ans = 0, tmp;
while(true){
// 反正就一直增廣到不行
tmp = findAugPath(s, INF);
if(tmp == 0) break;
ans += tmp;
}
return ans;
}
不實用所以就不實作了。
很爛吧。想辦法再唬爛一點?
唬爛唬爛結果複雜度就變好了?
比剛剛好很多了!但是能再更好嗎?
反正通常也不會用到就是了。
void bfs(){
memset(level, -1, sizeof(level));
level[s] = 0;
queue<int> q; q.push(s);
while(!q.empty()){
int u = q.front(); q.pop();
for(Edge e: G[u]){
if(e.cap > 0 and level[e.to] == -1){
level[e.to] = level[u] + 1;
q.push(e.to);
}
}
}
}
int dfs(int u, int flow){
if(u == t) return flow;
// 下面這行是重點!
for(int &i = iter[u]; i < (int) G[u].size(); ++i){
Edge &e = G[u][i];
if(e.cap > 0 and level[e.to] == level[u] + 1){
int ret = dfs(e.to, min(flow, e.cap));
if(ret > 0){
e.cap -= ret;
G[e.to][e.rev].cap += ret;
return ret;
}
}
}
return 0;
}
有 \(n \le 40\) 個點 \(1 \sim n\) ,\(m\) 條長度為 \(1\) 的無向邊,
保證這張圖是聯通的。
每一個點有一個「期望距離 \(want_i\)」,
假設點 \(i\) 與點 \(1\) 的實際距離為 \(real_i\),
失望值即為 \((want_i - real_i) ^ 2\)。
你可以對這張圖增加一些長度為 \(1\) 的無向邊,
求失望值和最小值,
以及要加上哪些邊。
注意到不需要最小化增加的邊的數量。
怎麼看都不是 Flow 對吧。
不要中毒
CodeForces 上面一排題目有 Flow tag 的官解一點關係都沒有(就算看起來有像也是),有時候就算是 Flow 的題目也不一定要寫出 Flow 演算法。
不要仲毒
這只是 Flow 的最最基礎的一小部分而已,還有一大堆講師不會、沒講的
不要中毒,很重要,不要看到就砸耍 Dinic
主要不是這篇文章,是他底下的留言和超連結們。
關鍵字:Preflow-Push
實際上大多數情況還是 Dinic 快。
平面圖似乎能做到 \(O(N \log N)\)?