< 설명 >
Max flow를 구하는 데 있어, PS에서 쓸 수 있는 알고리즘 중 가장 빠르다고 한다. O(V^2 * E)의 시간복잡도를 가진다. 설명은 아래에.
1. BFS로 유랑이 남은 간선을 기준으로 src에서 dst 노드까지 최단 거리를 구한다.(이를 level graph라고 함)
--> BFS라서 O(E)에 가능하다. 선형 시간!
2. DFS로 level graph에서 blocking flow를 흘려주면서 더 이상 못 보낼 때까지 반복한다.
--> 생각하기 좀 어려웠는데 한 번 flow를 흘려줄 때마다 bottleneck은 막혀버리니까 최소한 edge하나는 막혀버림. 그리고 edge가 막힐 때 dead end(가다가 막혀버리는 vertex)를 최대 V번 만날 수 있으므로, 많아야 VE번 증가경로를 찾고 끝난다.
3. 1, 2를 남은 유량이 없을 때까지 반복한다.
--> 최단 거리는 최대 모든 vertex가 다 관여할 때까지 가능하므로(V개의 vertex를 잇는 최단 거리는 최대 V-1개의 edge) V번 반복한다.
그래서 O(V) * O(VE) = O(V^2*E) 가 된다.
다른 좋은 블로그에서 상세한 설명은 잘 해두셔서 나는 추가로 더 참고한 유튜브 링크를 걸어두겠다.
https://www.youtube.com/watch?v=M6cm8UeeziI
https://www.youtube.com/watch?v=KpZjBoi_H6s
https://www.youtube.com/watch?v=UMT4Nyl8JAA
< 공부한 코드 >
** 역방향 간선을 나타내는 방식이 그전에 봤던 방식과 다르다. emplace_back을 이용해서 역방향의 idx를 맨 뒤에 저장하는 방식.
** DFS할 때 int& i 이렇게 reference를 하는 이유는, flow가 남는 것이 없거나 level 차이가 1을 초과하는 edge를 가볍게 건너뛰고 탐색하기 위해서이다. DFS를 여러번 반복해야하므로 애초에 의미없는 edge는 여러 번 확인하지 않는다는 의미. 그래서 level 그래프 새로 그릴 때마다 매번 새롭게 구해야 한다. 만약 그리고 edge의 용량을 남기고 종료하는 경우는 i가 증가되지 않은 채로 return이 되버리기 때문에 그 edge부터 다시 탐색하므로, 다른 경로에서 해당 edge가 사용될 수 있다. 걱정하지 않아도 된다!
#include <bits/stdc++.h>
using namespace std;
#define INF 987654321
typedef long long ll;
typedef pair<int, int> ii;
typedef tuple<int, int, int> iii;
#define pb push_back
#define N_MAX 60
#define src 0 // A
#define dst 25 // Z
int level[N_MAX]; // 레벨그래프
int checked[N_MAX]; // 이미 확인한 엣지는 확인안하기 위해서 두는 배열
struct Edge { // u -> v
int v, cap, rev;
Edge(int v, int cap, int rev) :v(v), cap(cap), rev(rev) {}
};
vector<vector<Edge> > graph; // graph
// 역방향을 고려한 edge 추가
void addEdge(int a, int b, int cap) {
graph[a].emplace_back(b, cap, (int)graph[b].size());
graph[b].emplace_back(a, cap, (int)graph[a].size() - 1);
}
bool bfs(){
memset(level, -1, sizeof(level)); // level idx
queue<int> q;
level[src] = 0; // src의 level은 0
q.push(src);
while(!q.empty()){
int here = q.front();
q.pop();
for (auto i : graph[here]){
int there = i.v;
int c = i.cap;
if (level[there] == -1 && c > 0){ // 조건에 맞는 점들을 bfs
level[there] = level[here] + 1;
q.push(there);
}
}
}
if (level[dst] == -1) return false; // 도달 불가능
else return true;
}
int dfs(int here, int bottleneck){
if (here == dst) return bottleneck; // bottleneck이었던 유량을 return
// 이미 확인한 edge를 반복해서 확인하기 싫어서 &i 사용
for(int &i = checked[here]; i < graph[here].size(); i++){
int there = graph[here][i].v;
int c = graph[here][i].cap;
if (level[there] == level[here]+1 && c > 0){ // 조건에 맞는 노드인가?
int final_btn = dfs(there, min(bottleneck, c)); // 흐를 수 있는 최대유량은 최소값으로
if (final_btn > 0) { // 끝까지 가서 찾은 final bottleneck이 유효한 값인가?
graph[here][i].cap -= final_btn;
graph[there][graph[here][i].rev].cap += final_btn; // 역방향 간선 고려
return final_btn; // 이거로 탐색하다 중단하면 이 i부터 다시 시작!!
}
}
}
return 0;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int N;
cin >> N;
graph.resize(N_MAX);
for(int i=0; i<N;i++){
char a, b; int d; cin >> a >> b >> d;
int nu, nv;
if ('A' <= a && a <= 'Z'){
nu = a - 'A';
}
else nu = a - 'a' + 26;
if ('A' <= b && b <= 'Z'){
nv = b - 'A';
}
else nv = b - 'a' + 26;
addEdge(nu, nv, d);
}
int ans = 0;
while (bfs()){ // level graph : O(V)
memset(checked, 0, sizeof(checked));
while(1){
int f = dfs(src, INF); : // O(VE)
if (f == 0) break; // 더 이상 흘려보낼 유량이 없다.
ans += f;
}
}
cout << ans << '\n';
return 0;
}
오늘의 공부 끝! 시험기간인데 알고리즘 공부 너무 재밌다ㅎㅎㅎ
'컴퓨터 사이언스 > 알고리즘 관련 정리사항' 카테고리의 다른 글
[VS Code] 디버깅 설정, Intellisense 관련 오류 (0) | 2019.10.10 |
---|---|
[알고리즘 공부] 트리의 구현과 순회 (0) | 2019.07.06 |
c++ binary_search (0) | 2018.11.10 |
댓글