Download Speed | Police Chase |
School Dance | Distinct Routes |
Parcel Delivery | Task Assignment |
Distinct Routes II | Distance Queries |
Grid Puzzle I | Grid Puzzle II |
115、116、117 今天不會講
1
1
1
1
2
2
3
$$|f|=2$$
一個流網路(Flow Network)是一張有向圖 \(G=(V,E)\)
一個流網路上的 s-t 可行流是一個函數 \(f:V \times V \mapsto \mathbb{R} \) 滿足:
一個流網路上的 s-t 可行流是一個函數 \(f:V \times V \mapsto \mathbb{R} \) 滿足:
這個 s-t 流的流量為
\(|f|=\sum\limits_{v \in V} f(s,v)=\sum\limits_{u \in t} f(u,t)\)
一個流網路上,\(|f|\)最大的 s-t 流是一個最大流
這個網路的最大流是什麼?
這個網路的最大流是什麼?
\(|f|=4+3+3+1=11\)
剛剛怎麼找最大流的?
剛剛怎麼找最大流的?
Greedy 亂做?
每次找一條 s-t 的路流
剛剛怎麼找最大流的?
Greedy 亂做?
每次找一條 s-t 的路流
剛剛怎麼找最大流的?
Greedy 亂做?
每次找一條 s-t 的路流
\(|f|=1\)
Greedy
最大流
差了什麼?
Greedy
最大流
差了什麼?
流回去
可以:
可以:
Recall: \(f(2,1)=-f(1,2)=-3\)、\(c(2,1)=0\)
定義:兩點間的剩餘流量 (residual capacity) 為:
\(r(u,v)=c(u,v)-f(u,v)\)
也就是這條邊還可以再(倒)流的量
定義:兩點間的剩餘流量 (residual capacity) 為:
\(r(u,v)=c(u,v)-f(u,v)\)
也就是這條邊還可以再(倒)流的量
所有 \(r(u,v)>0\) 的邊構成的圖稱為剩餘網路 (residual network)
圖 \(G\) 上的一組流 \(f\) 的剩餘網路記作 \(G_f\)
定義:兩點間的剩餘流量 (residual capacity) 為:
\(r(u,v)=c(u,v)-f(u,v)\)
也就是這條邊還可以再(倒)流的量
所有 \(r(u,v)>0\) 的邊構成的圖稱為剩餘網路 (residual network)
圖 \(G\) 上的一組流 \(f\) 的剩餘網路記作 \(G_f\)
剩餘網路上一組 s-t 路徑稱為擴充路徑(Augmenting Path)
屁啦
證明呢
圖的「割」:把圖分成兩群
中間的邊把他🈹掉
圖的「割」:把圖分成兩群
中間的邊把他🈹掉
定義:一個 s-t 割 (cut) \(C\) 是一個滿足 \(s \in S, t \in T\) 的割
任意一組 s-t 流 \(\leq\) 任意一組 s-t 割
任意一組 s-t 流 \(\leq\) 任意一組 s-t 割
最大流 \(\leq\) 最小割!
到底找不找得到呢?
任意一組 s-t 流 \(\leq\) 任意一組 s-t 割
最大流 \(\leq\) 最小割!
到底找不找得到呢?可以!
(Max Flow Min Cut Theorem)
對一個流量網路 \(G\) ,以下三者等價:
(Max Flow Min Cut Theorem)
對一個流量網路 \(G\) ,以下三者等價:
也就是 最大流 = 最小割,而且greedy亂做是對的
(Max Flow Min Cut Theorem)
證明:(1) -> (2)
假設 \(G_f\) 有擴充路徑,那走完這條之後 \(|f|\) 會更大,矛盾
(Max Flow Min Cut Theorem)
證明:(2) -> (3)
令 \(S\) 為 \(s\) 在 \(G_f\) 上所有能走到的點,\(T=V \backslash S\)
(Max Flow Min Cut Theorem)
證明:(2) -> (3)
令 \(S\) 為 \(s\) 在 \(G_f\) 上所有能走到的點,\(T=V \backslash S\)
(Max Flow Min Cut Theorem)
證明:(3) -> (1)
(Max Flow Min Cut Theorem)
證明:(3) -> (1)
構解:\(S\) 為 \(s\) 在 \(G_f\) 上所有能走到的點,\(T=V \backslash S\)
Ford-Fulkerson & Capacity Scaling
MFMC 說 greedy 是對的
MFMC 說 greedy 是對的
演算法:
好簡單
MFMC 說 greedy 是對的
演算法:
好簡單
找路徑?DFS
流多少?擴充路徑上剩餘流量的最小值
#include<iostream>
#include<vector>
#include<algorithm>
#include<utility>
using namespace std;
typedef long long ll;
const ll inf = INT32_MAX;
struct edge{
int v, c, r, rid;
edge(int _v, int _c, int _r, int _rid){
v = _v, c = _c, r = _r, rid = _rid;
}
// rid: 存反向邊編號
};
struct ff{
vector<vector<edge>> graph;
vector<bool> vis;
ff(int n){
graph.resize(n + 1);
vis.resize(n + 1);
}
void add(int a, int b, int c, int rc = 0){
// rc: 無向圖時用
graph[a].emplace_back(b, c, c, (int)graph[b].size());
graph[b].emplace_back(a, rc, rc, (int)graph[a].size() - 1);
}
int dfs(int u, int t, int cap){
// cap: 瓶頸大小
if(cap == 0) return 0; // 涼了
if(u == t) return cap; // 到t了
vis[u] = 1;
for(auto &[v, c, r, rid]: graph[u]){
if(!vis[v]){
int flow_val = dfs(v, t, min(cap, r));
if(flow_val != 0){
r -= flow_val;
graph[v][rid].r += flow_val;
return flow_val;
}
}
}
return 0;
}
ll flow(int s, int t){
// 記得開long long
ll ans = 0, flow_val;
while((flow_val = dfs(s, t, inf)) > 0){
ans += flow_val;
fill(vis.begin(), vis.end(), 0);
}
return ans;
}
};
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int n, m, s, t;
cin >> n >> m >> s >> t;
ff flow(n);
int a, b, c;
for(int i = 0; i < m; i++){
cin >> a >> b >> c;
flow.add(a, b, c);
}
cout << flow.flow(s, t) << "\n";
}
唬爛2:
假設最大邊權為 \(U\)
先只看 \(G_f\) 中剩餘流量 \(\geq\) \(U\) 的邊
再看 \(\geq \frac{U}{2}\) 的邊
再看 \(\geq \frac{U}{4}\) 的邊
...
#include<iostream>
#include<vector>
#include<algorithm>
#include<utility>
using namespace std;
typedef long long ll;
const ll inf = INT32_MAX;
struct edge{
int v, c, r, rid;
edge(int _v, int _c, int _r, int _rid){
v = _v, c = _c, r = _r, rid = _rid;
}
// rid: 存反向邊編號
};
struct ff{
vector<vector<edge>> graph;
vector<bool> vis;
int maxi = 0;
ff(int n){
graph.resize(n + 1);
vis.resize(n + 1);
}
void add(int a, int b, int c, int rc = 0){
// rc: 無向圖時用
graph[a].emplace_back(b, c, c, (int)graph[b].size());
graph[b].emplace_back(a, rc, rc, (int)graph[a].size() - 1);
maxi = max(maxi, c);
maxi = max(maxi, rc);
}
int dfs(int u, int t, int cap, int delta){
// cap: 瓶頸大小
if(cap < delta) return 0; // 涼了
if(u == t) return cap; // 到t了
vis[u] = 1;
for(auto &[v, c, r, rid]: graph[u]){
if(!vis[v]){
int flow_val = dfs(v, t, min(cap, r), delta);
if(flow_val != 0){
r -= flow_val;
graph[v][rid].r += flow_val;
return flow_val;
}
}
}
return 0;
}
ll flow(int s, int t, int delta){
// 記得開long long
ll ans = 0, flow_val;
while((flow_val = dfs(s, t, inf, delta)) > 0){
ans += flow_val;
fill(vis.begin(), vis.end(), 0);
}
return ans;
}
ll scale(int s, int t){
ll ans = 0;
while(maxi > 0){
fill(vis.begin(), vis.end(), 0);
ans += flow(s, t, maxi);
maxi /= 2;
}
return ans;
}
};
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int n, m, s, t;
cin >> n >> m >> s >> t;
ff flow(n);
int a, b, c;
for(int i = 0; i < m; i++){
cin >> a >> b >> c;
flow.add(a, b, c);
}
cout << flow.scale(s, t) << "\n";
}
複雜度突然變 \(|E|^2 logU\) 了!
複雜度突然變 \(|E|^2 logU\) 了!
證明:
複雜度突然變 \(|E|^2 logU\) 了!
證明:
複雜度突然變 \(|E|^2 logU\) 了!
證明:
複雜度突然變 \(|E|^2 logU\) 了!
證明:
複雜度突然變 \(|E|^2 logU\) 了!
其實這樣已經可以過所有CSES題了 (flow很難跑到 worst-case)
Edmonds-Karp, Dinic (Dinitz)
演算法:
演算法:
演算法:
\(O(|E|\cdot |f|)\) -> \(O(|V|\cdot|E|^2)\)!
兩個 lemma:
兩個 lemma:
一次擴充一條緊邊 -> 最多擴充 \(O(VE)\) 次 -> 總複雜度 \(O(VE^2)\)
Lemma 1 證明:
Lemma 2 證明:
#include<iostream>
#include<vector>
#include<algorithm>
#include<utility>
#include<queue>
using namespace std;
typedef long long ll;
const ll inf = INT32_MAX;
struct edge{
int v, c, r, rid;
edge(int _v, int _c, int _r, int _rid){
v = _v, c = _c, r = _r, rid = _rid;
}
// rid: 存反向邊編號
};
struct ek{
vector<vector<edge>> graph;
vector<bool> vis;
vector<int> dist;
ek(int n){
graph.resize(n + 1);
vis.resize(n + 1);
dist.resize(n + 1);
}
void add(int a, int b, int c, int rc = 0){
// rc: 無向圖時用
graph[a].emplace_back(b, c, c, (int)graph[b].size());
graph[b].emplace_back(a, rc, rc, (int)graph[a].size() - 1);
}
bool bfs(int s, int t){
fill(dist.begin(), dist.end(), inf);
queue<int> q;
q.push(s);
dist[s] = 0;
while(!q.empty()){
auto node = q.front();
q.pop();
for(auto &[v, c, r, rid]: graph[node]){
if(r > 0 && dist[v] == inf){
dist[v] = dist[node] + 1;
q.push(v);
}
}
}
return dist[t] < inf;
}
int dfs(int u, int t, int cap){
// cap: 瓶頸大小
if(cap == 0) return 0; // 涼了
if(u == t) return cap; // 到t了
vis[u] = 1;
for(auto &[v, c, r, rid]: graph[u]){
if(!vis[v] && dist[v] == dist[u] + 1){
int flow_val = dfs(v, t, min(cap, r));
if(flow_val != 0){
r -= flow_val;
graph[v][rid].r += flow_val;
return flow_val;
}
}
}
return 0;
}
ll flow(int s, int t){
// 記得開long long
ll ans = 0, flow_val;
while(bfs(s, t)){
fill(vis.begin(), vis.end(), 0);
while((flow_val = dfs(s, t, inf)) > 0){
ans += flow_val;
fill(vis.begin(), vis.end(), 0);
}
}
return ans;
}
};
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int n, m, s, t;
cin >> n >> m >> s >> t;
ek flow(n);
int a, b, c;
for(int i = 0; i < m; i++){
cin >> a >> b >> c;
flow.add(a, b, c);
}
cout << flow.flow(s, t) << "\n";
}
#include<iostream>
#include<vector>
#include<algorithm>
#include<utility>
#include<queue>
using namespace std;
typedef long long ll;
const ll inf = INT32_MAX;
struct edge{
int v, c, r, rid;
edge(int _v, int _c, int _r, int _rid){
v = _v, c = _c, r = _r, rid = _rid;
}
// rid: 存反向邊編號
};
struct ek{
vector<vector<edge>> graph;
vector<bool> vis;
vector<int> dist;
int maxi = 0;
ek(int n){
graph.resize(n + 1);
vis.resize(n + 1);
dist.resize(n + 1);
}
void add(int a, int b, int c, int rc = 0){
// rc: 無向圖時用
graph[a].emplace_back(b, c, c, (int)graph[b].size());
graph[b].emplace_back(a, rc, rc, (int)graph[a].size() - 1);
maxi = max(maxi, c);
maxi = max(maxi, rc);
}
bool bfs(int s, int t, int delta){
fill(dist.begin(), dist.end(), inf);
queue<int> q;
q.push(s);
dist[s] = 0;
while(!q.empty()){
auto node = q.front();
q.pop();
for(auto &[v, c, r, rid]: graph[node]){
if(r >= delta && dist[v] == inf){
dist[v] = dist[node] + 1;
q.push(v);
}
}
}
return dist[t] < inf;
}
int dfs(int u, int t, int cap, int delta){
// cap: 瓶頸大小
if(cap < delta) return 0; // 涼了
if(u == t) return cap; // 到t了
vis[u] = 1;
for(auto &[v, c, r, rid]: graph[u]){
if(!vis[v] && dist[v] == dist[u] + 1){
int flow_val = dfs(v, t, min(cap, r), delta);
if(flow_val != 0){
r -= flow_val;
graph[v][rid].r += flow_val;
return flow_val;
}
}
}
return 0;
}
ll flow(int s, int t, int delta){
// 記得開long long
ll ans = 0, flow_val;
while(bfs(s, t, delta)){
fill(vis.begin(), vis.end(), 0);
while((flow_val = dfs(s, t, inf, delta)) > 0){
ans += flow_val;
fill(vis.begin(), vis.end(), 0);
}
}
return ans;
}
ll scale(int s, int t){
ll ans = 0;
while(maxi > 0){
fill(vis.begin(), vis.end(), 0);
ans += flow(s, t, maxi);
maxi /= 2;
}
return ans;
}
};
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int n, m, s, t;
cin >> n >> m >> s >> t;
ek flow(n);
int a, b, c;
for(int i = 0; i < m; i++){
cin >> a >> b >> c;
flow.add(a, b, c);
}
cout << flow.scale(s, t) << "\n";
}
當然也可以 scaling
Edmonds-Karp 浪費時間在哪?
每次最短路都要重跑一次
Edmonds-Karp 浪費時間在哪?
每次最短路都要重跑一次
先把最短路 DAG 找出來
假設現在有條邊沒有用了..
假設現在有條邊沒有用了..
下次直接從下一條邊開始找擴充路徑!
假設現在有條邊沒有用了..
下次直接從下一條邊開始找擴充路徑!
對於每個點 \(u\) ,紀錄這個點目前跑到的邊 \(it(u)\)
下次直接從 \(it(u)\) 開始跑
#include<iostream>
#include<vector>
#include<algorithm>
#include<utility>
#include<queue>
using namespace std;
typedef long long ll;
const ll inf = INT32_MAX;
struct edge{
int v, c, r, rid;
edge(int _v, int _c, int _r, int _rid){
v = _v, c = _c, r = _r, rid = _rid;
}
// rid: 存反向邊編號
};
struct dinic{
vector<vector<edge>> graph;
vector<bool> vis;
vector<int> dist, it;
dinic(int n){
graph.resize(n + 1);
vis.resize(n + 1);
dist.resize(n + 1);
it.resize(n + 1);
}
void add(int a, int b, int c, int rc = 0){
// rc: 無向圖時用
graph[a].emplace_back(b, c, c, (int)graph[b].size());
graph[b].emplace_back(a, rc, rc, (int)graph[a].size() - 1);
}
bool bfs(int s, int t){
fill(dist.begin(), dist.end(), inf);
queue<int> q;
q.push(s);
dist[s] = 0;
while(!q.empty()){
auto node = q.front();
q.pop();
for(auto &[v, c, r, rid]: graph[node]){
if(r > 0 && dist[v] == inf){
dist[v] = dist[node] + 1;
q.push(v);
}
}
}
return dist[t] < inf;
}
int dfs(int u, int t, int cap){
// cap: 瓶頸大小
if(cap == 0) return 0; // 涼了
if(u == t) return cap; // 到t了
vis[u] = 1;
for(int &i = it[u]; i < (int)graph[u].size(); i++){
auto &[v, c, r, rid] = graph[u][i];
if(!vis[v] && dist[v] == dist[u] + 1){
int flow_val = dfs(v, t, min(cap, r));
if(flow_val != 0){
r -= flow_val;
graph[v][rid].r += flow_val;
return flow_val;
}
}
}
return 0;
}
ll flow(int s, int t){
// 記得開long long
ll ans = 0, flow_val;
while(bfs(s, t)){
fill(vis.begin(), vis.end(), 0);
fill(it.begin(), it.end(), 0);
while((flow_val = dfs(s, t, inf)) > 0){
ans += flow_val;
fill(vis.begin(), vis.end(), 0);
}
}
return ans;
}
};
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int n, m, s, t;
cin >> n >> m >> s >> t;
dinic flow(n);
int a, b, c;
for(int i = 0; i < m; i++){
cin >> a >> b >> c;
flow.add(a, b, c);
}
cout << flow.flow(s, t) << "\n";
}
#include<iostream>
#include<vector>
#include<algorithm>
#include<utility>
#include<queue>
using namespace std;
typedef long long ll;
const ll inf = INT32_MAX;
struct edge{
int v, c, r, rid;
edge(int _v, int _c, int _r, int _rid){
v = _v, c = _c, r = _r, rid = _rid;
}
// rid: 存反向邊編號
};
struct dinic{
vector<vector<edge>> graph;
vector<bool> vis;
vector<int> dist, it;
int maxi = 0;
dinic(int n){
graph.resize(n + 1);
vis.resize(n + 1);
dist.resize(n + 1);
it.resize(n + 1);
}
void add(int a, int b, int c, int rc = 0){
// rc: 無向圖時用
graph[a].emplace_back(b, c, c, (int)graph[b].size());
graph[b].emplace_back(a, rc, rc, (int)graph[a].size() - 1);
maxi = max(maxi, c);
maxi = max(maxi, rc);
}
bool bfs(int s, int t, int delta){
fill(dist.begin(), dist.end(), inf);
queue<int> q;
q.push(s);
dist[s] = 0;
while(!q.empty()){
auto node = q.front();
q.pop();
for(auto &[v, c, r, rid]: graph[node]){
if(r > 0 && dist[v] == inf && r >= delta){
dist[v] = dist[node] + 1;
q.push(v);
}
}
}
return dist[t] < inf;
}
int dfs(int u, int t, int cap, int delta){
// cap: 瓶頸大小
if(cap < delta) return 0; // 涼了
if(u == t) return cap; // 到t了
vis[u] = 1;
for(int &i = it[u]; i < (int)graph[u].size(); i++){
auto &[v, c, r, rid] = graph[u][i];
if(!vis[v] && dist[v] == dist[u] + 1){
int flow_val = dfs(v, t, min(cap, r), delta);
if(flow_val != 0){
r -= flow_val;
graph[v][rid].r += flow_val;
return flow_val;
}
}
}
return 0;
}
ll flow(int s, int t, int delta){
// 記得開long long
ll ans = 0, flow_val;
while(bfs(s, t, delta)){
fill(vis.begin(), vis.end(), 0);
fill(it.begin(), it.end(), 0);
while((flow_val = dfs(s, t, inf, delta)) > 0){
ans += flow_val;
fill(vis.begin(), vis.end(), 0);
}
}
return ans;
}
ll scale(int s, int t){
ll ans = 0;
while(maxi > 0){
ans += flow(s, t, maxi);
maxi /= 2;
}
return ans;
}
};
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int n, m, s, t;
cin >> n >> m >> s >> t;
dinic flow(n);
int a, b, c;
for(int i = 0; i < m; i++){
cin >> a >> b >> c;
flow.add(a, b, c);
}
cout << flow.scale(s, t) << "\n";
}
又可以 scaling 了
\(O(V^2E)\)
可以想像成把Edmonds-Karp的一個 \(E\) 壓成 \(V\)
證明:
更爽的特例:
二分圖匹配是 \(O(\sqrt{V} E)\) 的證明:
講師不會備課所以聽不懂很正常
可以先把 Dinic 想像成一個會輸出最大流的黑盒子
有 \(n\) 台機器跟 \(m\) 條電纜,每條電纜連接 \(u,v)\),有一個頻寬上限 \(c\)
請問從機器 1 傳訊到機器 \(n\) 的最大頻寬為多少?
最小割裸題
還記得怎麼構解嗎?
有 \(n\) 個男生跟 \(m\) 個女生,有 \(k\) 對男生跟女生想一起跳舞
一個人只能跟另一個人跳舞,請問最多可以湊成幾對舞伴?
P. S. Happy Pride Month
二分圖匹配
\(s\) 跟 \(t\) 怎麼辦?
二分圖匹配
\(s\) 跟 \(t\) 怎麼辦?
把所有源點連到超源點、所有匯點連到超匯點
這些是上課可能會講得例題
Min Cost Max Flow
一個費用流網路(Flow Network)是一張有向圖 \(G=(V,E)\)
一個費用流網路上的 s-t 可行流是一個函數 \(f:V \times V \mapsto \mathbb{R} \) 滿足:
這個 s-t 流的流量為
\(|f|=\sum\limits_{v \in V} f(s,v)=\sum\limits_{u \in t} f(u,t)\)
這個 s-t 流的費用為
\(W(f)=\sum\limits_{(u,v) \in E} f(u,v)W(u,v)\)
最小費用最大流:流量是最大的情況下,費用最少的流
這個 s-t 流的流量為
\(|f|=\sum\limits_{v \in V} f(s,v)=\sum\limits_{u \in t} f(u,t)\)
這個 s-t 流的費用為
\(W(f)=\sum\limits_{(u,v) \in E} f(u,v)W(u,v)\)
Min Cost Max Flow
跟 Ford-Fulkerson 一模一樣!
演算法:
Min Cost Max Flow
跟 Ford-Fulkerson 一模一樣!
演算法:
小問題:剩餘流量的花費?
Min Cost Max Flow
跟 Ford-Fulkerson 一模一樣!
演算法:
小問題:剩餘流量的花費?
取消花 \(W(u,v)\) 的錢 -> 花 \(-W(u,v)\) 的錢
Min Cost Max Flow
Min Cost Max Flow
注意到最小花費的路不一定是最短路
所以複雜度退化到 Ford-Fulkerson 的\(O(SP\cdot |f|)\)
其中 SP 為一次最短路要花的時間
因為負邊會憑空跑出來 所以要用 SPFA (很重要)
但是可以證明如果一開始的圖沒有負環,那之後都不會有
#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll inf = INT64_MAX;
struct edge{
int v, c, r, rid;
ll w;
edge(int _v, int _c, int _r, int _rid, int _w){
v = _v, c = _c, r = _r, rid = _rid, w = _w;
}
};
struct mcmf{
vector<vector<edge>> graph;
vector<int> par, par_eid;
vector<ll> dist;
vector<bool> inq;
mcmf(int n){
graph.resize(n + 1);
dist.resize(n + 1);
par.resize(n + 1);
inq.resize(n + 1);
par_eid.resize(n + 1);
}
void add(int a, int b, int c, ll w){
graph[a].emplace_back(b, c, c, (int)graph[b].size(), w);
graph[b].emplace_back(a, 0, 0, (int)graph[a].size() - 1, -w);
}
bool spfa(int s, int t){
fill(inq.begin(), inq.end(), 0);
fill(dist.begin(), dist.end(), inf);
queue<int> q;
q.emplace(s);
inq[s] = 1;
dist[s] = 0;
while(!q.empty()){
int u = q.front();
q.pop();
inq[u] = 0;
for(int i = 0; i < (int)graph[u].size(); i++){
auto &[v, c, r, rid, w] = graph[u][i];
if(dist[u] + w < dist[v] && r > 0){
dist[v] = dist[u] + w;
par[v] = u;
par_eid[v] = i;
if(!inq[v]) q.push(v), inq[v] = 1;
}
}
}
return dist[t] < inf;
}
pair<ll, ll> flow(int s, int t){
ll ans = 0, mf = 0;
while(spfa(s, t)){
ll cap = inf;
int cur = t;
while(cur != s){
cap = min(cap, (ll)graph[par[cur]][par_eid[cur]].r);
cur = par[cur];
}
cur = t;
while(cur != s){
ans += cap * graph[par[cur]][par_eid[cur]].w;
graph[par[cur]][par_eid[cur]].r -= cap;
graph[cur][graph[par[cur]][par_eid[cur]].rid].r += cap;
cur = par[cur];
}
mf += cap;
}
return make_pair(mf, ans);
}
};
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int n, m;
cin >> n >> m;
mcmf flow(n);
int a, b, c, w;
for(int i = 0; i < m; i++){
cin >> a >> b >> c >> w;
flow.add(a, b, c, w);
}
auto res = flow.flow(1, n);
cout << res.first << " " << res.second << "\n";
}
假設我想找流量為 \(x\) 的最小費用流?
假設我想找流量為 \(x\) 的最小費用流?
跑 \(x\) 次 SPFA 就好,可以證明是好的
假設我想找流量為 \(x\) 的最小費用流?
跑 \(x\) 次 SPFA 就好,可以證明是好的
MCMF 時最短路不會變短,跟前面一樣
假設我想找流量為 \(x\) 的最小費用流?
跑 \(x\) 次 SPFA 就好,可以證明是好的
MCMF 時最短路不會變短,跟前面一樣
假設 \(f(x)\) 為流量為 \(x\) 的最小費用流,\(f(x)\) 會是凸的,
因為 \(f(x+1)-f(x) \geq f(x) - f(x-1)\)
這個性質後面可以用
XXXXXX
XXXXXXXX
XXXXXX
XXXXXXXXXXXX
XXXXXX
XXXXXXXX
XXXXXX
XXXXXXXXXXXX
可以建模跑 flow 欸!
XXXXXX
XXXXXXXX
XXXXXX
XXXXXXXXXXXX
我需要解釋
有時候看起來可以 flow
但範圍大到不能 flow
那可能可以用 flow 的一些酷性質導出正解
有一條路上有 \(n) 個城市
第 \(i\) 個城市出產 \(p_i\) 公斤的產品 最多可以賣 \(s_i\) 公斤的產品
對所有 \(1 \leq i < j \leq n\) 的 \((i,j)\),最多可以從 \(i\) 送 \(c\) 公斤的產品到 \(j\)
問最多能賣多少產品
顯然是最大流..
(自己建模看看)
不能算最大流,那能不能算最小割?
令 \(dp(i,j)\) 為前 \(i\) 個城市中,有 \(j\) 個城市在 \(S\) 中的最小割
轉移:\(dp(i,j)=min(dp(i-1,j-1)+s_i,dp(i-1,j)+p_i+c*j)\)
複雜度 \(O(n^2)\),記得滾動
前情提要:MCMF 的做法大致上有兩種
前情提要:MCMF 的做法大致上有兩種
前情提要:MCMF 的做法大致上有兩種
模擬費用流:試著用更快的方法模擬這兩種演算法
poohorz 要辦一場模競,總共有 \(k\) 題
他可以花 \(n\) 天準備,其中第 \(i\) 天可以:
請問最少需要花多少錢?
先建模吧
一條擴增路徑一定是:\(s\rightarrow i \rightarrow j \rightarrow t\),而且
一條擴增路徑一定是:\(s\rightarrow i \rightarrow j \rightarrow t\),而且
二分搜一個 \(c\)
考慮從 \(n\) 到 \(1\) 一個一個加點到現在的圖裡面
加了一個點 \(i\) 之後會有兩種負環可以消:
考慮從 \(n\) 到 \(1\) 一個一個加點到現在的圖裡面
加了一個點 \(i\) 之後會有兩種負環可以消:
考慮從 \(n\) 到 \(1\) 一個一個加點到現在的圖裡面
加了一個點 \(i\) 之後會有兩種負環可以消:
2. \(s \rightarrow a_i \rightarrow b_j \rightarrow a_k \rightarrow s\)
考慮從 \(n\) 到 \(1\) 一個一個加點到現在的圖裡面
加了一個點 \(i\) 之後會有兩種負環可以消:
1. 把 \(a_i\) 跟 \(b_j\) 配
2. 把一對 \((a_k,b_j)\) 幹掉,改成 \((a_i,b_j)\)
事實上還有更多奇怪的負環,但只需要考慮上面這兩種就好,可以證明看看
考慮從 \(n\) 到 \(1\) 一個一個加點到現在的圖裡面
加了一個點 \(i\) 之後會有兩種負環可以消:
開一個 pq 存目前可能被配的東西
每次 pop 最小的配 (如果加起來小於0)
配完要把可能跟別人配的 push 進去: