[C++]최대 유량 - 백준6086

문제



풀이

문제 해석을 하자면,

  • 문제에선 양방향에 A~Z까지 최대 유량 출력 요구
  • 또한, 입력으로 A~Z, a~z 로 총 노드 52
  • 또한, 파이프가 합쳐지기도 한다했으니 용량을 +로 기입
    • 참고로 용량과 유량이 같으면 포화상태이므로 접근 못함


풀이

  • Ford-Fulkerson(DFS.복잡도:flow), Edmonds-Karp(BFS.복잡도:VE^2), Dinic’s Algorithm(BFS.복잡도:EV^2) 로 구현



코드

// BFS
#include<iostream>
#include<vector> // tree
#include<queue> // bfs
#include<cstring> // memset
#include<algorithm>
using namespace std;

int N; // 간선 수
int c[52][52]; // 용량 => 크기 : 대문자, 소문자 합 26+26
int f[52][52]; // 유량
vector<int> vc[52]; // tree
int visited[52];

int getID(char c) {
	if (c <= 'Z')
		return c - 'A';
	return c - 'a' + 26;
}

int main() {
	// speed up
	ios::sync_with_stdio(0);
	cin.tie(0);

	// input
	cin >> N;
	char c1, c2;
	int w;
	for (int i = 0; i < N; i++) {
		cin >> c1 >> c2 >> w;
		int n1 = getID(c1); int n2 = getID(c2);
		vc[n1].push_back(n2);
		vc[n2].push_back(n1); // 양방향
		c[n1][n2] += w; // 양방향 간선이므로, 양쪽으로 용량 할당
		c[n2][n1] += w;
	}

	// run
	int maximumFlow = 0, s = 0, t = 25; // s:A, t:Z
	while (true) {
		// init
		fill(visited, visited + 52, -1);
		queue<int> qu; // bfs
		qu.push(s);
		while (!qu.empty()) {
			int cur = qu.front();
			qu.pop();

			for (int i = 0; i < vc[cur].size(); i++) {
				int nxt = vc[cur][i];
				if (c[cur][nxt] <= f[cur][nxt] || visited[nxt] != -1) continue;
				visited[nxt] = cur; // 방문기록겸 이전경로 기록
				qu.push(nxt);

				// 인접 정점이 도착지일 경우, 하나의 증가 경로를 찾았으므로, 더 이상 탐색하지 않고 종료
				if (nxt == t) break;
			}
		}
		// 더이상 증가 경로가 없는경우 종료
		if (visited[t] == -1)break;

		// 최소 유량 탐색 => 증가 경로 하나 찾으면 바로 탈출했기 때문에 거꾸로 탐색해야함
		int minF = 10000000;
		for (int i = t; i != s; i = visited[i]) {
			int nxt = visited[i];
			minF = min(minF, c[nxt][i] - f[nxt][i]);
		}

		// 유량 update
		for (int i = t; i != s; i = visited[i]) {
			int nxt = visited[i];
			f[nxt][i] += minF; // 정방향
			f[i][nxt] -= minF; // 역방향
		}

		// 최대 유량 update
		maximumFlow += minF;
	}

	cout << maximumFlow;

	return 0;
}


// DFS
#include<iostream>
#include<vector> // tree
#include<stack> // dfs
#include<cstring> // memset
#include<algorithm>
using namespace std;

int N; // 간선 수
int c[52][52]; // 용량 => 크기 : 대문자, 소문자 합 26+26
int f[52][52]; // 유량
vector<int> vc[52]; // tree
int visited[52];

int getID(char c) {
	if (c <= 'Z')
		return c - 'A';
	return c - 'a' + 26;
}

int main() {
	// speed up
	ios::sync_with_stdio(0);
	cin.tie(0);

	// input
	cin >> N;
	char c1, c2;
	int w;
	for (int i = 0; i < N; i++) {
		cin >> c1 >> c2 >> w;
		int n1 = getID(c1); int n2 = getID(c2);
		vc[n1].push_back(n2);
		vc[n2].push_back(n1); // 양방향
		c[n1][n2] += w; // 양방향 간선이므로, 양쪽으로 용량 할당
		c[n2][n1] += w;
	}

	// run
	int maximumFlow = 0, s = 0, t = 25; // s:A, t:Z
	while (true) {
		// init
		fill(visited, visited + 52, -1);
		stack<int> st; // dfs
		st.push(s);
		while (!st.empty()) {
			int cur = st.top();
			st.pop();

			for (int i = 0; i < vc[cur].size(); i++) {
				int nxt = vc[cur][i];
				if (c[cur][nxt]<=f[cur][nxt]||visited[nxt] != -1) continue;
				visited[nxt] = cur; // 방문기록겸 이존경로 기록
				st.push(nxt);

				// 인접 정점이 도착지일 경우, 하나의 증가 경로를 찾았으므로, 더 이상 탐색하지 않고 종료
				if (nxt == t) break;
			}
		}
		// 더이상 증가 경로가 없는경우 종료
		if (visited[t] == -1)break;

		// 최소 유량 탐색 => 증가 경로 하나 찾으면 바로 탈출했기 때문에 거꾸로 탐색해야함
		int minF = 10000000;
		for (int i = t; i != s; i = visited[i]) {
			int nxt = visited[i];
			minF = min(minF, c[nxt][i] - f[nxt][i]);
		}

		// 유량 update
		for (int i = t; i != s; i = visited[i]) {
			int nxt = visited[i];
			f[nxt][i] += minF; // 정방향
			f[i][nxt] -= minF; // 역방향
		}

		// 최대 유량 update
		maximumFlow += minF;
	}

	cout << maximumFlow;

	return 0;
}


/*
우선 BFS를 이용하여 잔여용량이 0 이상인 간선에 대하여 레벨 그래프(level graph)라는 것을 만들어줍니다
- 레벨 그래프 : 시작노드로부터 최단 거리로 봐도됨. BFS로 바로 구하자.
- 만약 레벨 그래프가 안만들어지는 경우엔 바로 종료.
이후 DFS 재귀형태로 나머지 연산 짜는게 편한것 같다.

참고 : https://jason9319.tistory.com/150
*/

// Dinic's
#include<iostream>
#include<vector> // tree
#include<queue> // bfs
#include<algorithm> // fill
#define INF 987654321
#define S 0
#define T 25
using namespace std;


int N; // 간선 수
int c[55][55]; // 용량 => 크기 : 대문자, 소문자 합 26+26
int f[55][55]; // 유량
vector<int> vc[55]; // tree
int level[55]; // 레벨 그래프
int work[55];

int getID(char c) {
	if (c <= 'Z')
		return c - 'A';
	return c - 'a' + 26;
}

bool bfs() {
	// init
	fill(level, level + 52, -1);
	queue<int> qu; // bfs
	level[S] = 0;
	qu.push(S);
	while (!qu.empty()) {
		int cur = qu.front();
		qu.pop();

		for (int i = 0; i < vc[cur].size(); i++) {
			int nxt = vc[cur][i];
			if (c[cur][nxt] <= f[cur][nxt] || level[nxt] != -1) continue;
			// 레벨이 아직 정해지지 않았고 잔여용량이 0이상은 계속 진행
			level[nxt] = level[cur] + 1; // level 기록 (그냥 깊이 기록임)
			qu.push(nxt);
		}
	}
	return level[T] != -1; // -1 이면 false 반환(종료 의미)
}

int dfs(int cur, int flow) {
	// base condition
	if (cur == T) return flow; // cur -> depth로 이해

	for (int i = work[cur]; i < vc[cur].size(); i++) {
		int nxt = vc[cur][i];
		if (level[nxt] - level[cur] != 1 || c[cur][nxt] <= f[cur][nxt]) continue;
		// 레벨 차가 1이고, 잔여용량이 0이상은 계속 진행
		
		// 최소 유량 탐색
		int minF = dfs(nxt, min(c[cur][nxt] - f[cur][nxt], flow));

		// 유량 update
		if (minF > 0) {
			f[cur][nxt] += minF;
			f[nxt][cur] -= minF;
			work[cur] = i; // index 기록해서 시간 단축
			return minF;
		}
	}
	work[cur] = vc[cur].size();
	return 0;
}


int main() {
	// speed up
	ios::sync_with_stdio(0);
	cin.tie(0);

	// input
	cin >> N;
	char c1, c2;
	int w;
	for (int i = 0; i < N; i++) {
		cin >> c1 >> c2 >> w;
		int n1 = getID(c1); int n2 = getID(c2);
		vc[n1].push_back(n2);
		vc[n2].push_back(n1); // 양방향
		c[n1][n2] += w; // 양방향 간선이므로, 양쪽으로 용량 할당
		c[n2][n1] += w;
	}

	// run
	int maximumFlow = 0;
	while (true) {
		if (!bfs()) break; // 종료

		fill(work, work + 52, 0);
		int flow = dfs(S, INF);

		// 최대 유량 update
		maximumFlow += flow;
	}

	// output
	cout << maximumFlow;
	return 0;
}



느낀점

생략

댓글남기기