전공/알고리즘

백준 2211(네트워크 복구)

xkdlaldfjtnl 2020. 6. 25. 13:55

https://www.acmicpc.net/problem/2211

 

2211번: 네트워크 복구

첫째 줄에 두 정수 N, M이 주어진다. 다음 M개의 줄에는 회선의 정보를 나타내는 세 정수 A, B, C가 주어진다. 이는 A번 컴퓨터와 B번 컴퓨터가 통신 시간이 C (1 ≤ C ≤ 10)인 회선으로 연결되어 있다�

www.acmicpc.net

모든 연결이 끊겨 있는 상태에서 어떤 조건들을 만족하면서 최소의 연결방법을 구하는 문제이다. 

 

어떤 조건을 빼고 보면 최소 스패닝 트리라고 보면 된다. 

 

1부터 시작해서 인접한 곳에서 방문하지 않았던 지점 중 거리가 최소인 지점을 연결한다.  

이 결과는 총 연결 거리의 최소를 만들어 내는 알고리즘이다. 

 

하지만 문제에서 

 

  1. 네트워크를 복구해서 통신이 가능하도록 만드는 것도 중요하지만, 해커에게 공격을 받았을 때 보안 패킷을 전송하는 데 걸리는 시간도 중요한 문제가 된다. 따라서 슈퍼컴퓨터가 다른 컴퓨터들과 통신하는데 걸리는 최소 시간이, 원래의 네트워크에서 통신하는데 걸리는 최소 시간보다 커져서는 안 된다.

이렇게 조건이 주어져 있기 때문에 2->3까지의 연결 고리가 단순 최소라고 바로 이어서는 안된다. 

 

1->3 vs 1->2 + 2->3 을 비교해서 1->3 보다 크면 2->3을 연결하면 안된다.  

 

 dist[p2.second.second] < dist[p2.second.first] + p2.first

위 경우라면 문제 조건에 맞지 않으므로 패스

 

그래서 여기에 다익스트라 알고리즘이 추가된다. 

 

플로이드 와샬(모든 정점에서 다른 모든 정점까지 가는 모든 최단거리를 구하는 알고리즘) 을 생각했었지만 

보안 패킷은 1에서 시작하므로 그렇게 구할 필요도 없고, O(N^3) => 10^9이므로 시간초과가 난다. 

 

그래서 한 지점에서 다른 모든 지점까지 가는 다익스트라 알고리즘을 사용했다. 

 

 

먼저 1을 기준으로 최소 지점의 거리와 index를 구한다. 

그 다음 index를 지나면서 1->index + index->특정 지점 vs 1->특정지점을 비교해서 만약 1->특정지점보다 작으면 dist[]배열을 최신화 해준다. 이렇게 모든 지점을 통해서 다른 지점을 확인하고 나면, 1에서 다른 지점으로 가는 최단 거리 배열을 구할 수 있다. 

 

void di_init() {
	for (int i = 2; i <= N; i++) {
		dist[i] = conn[1][i];
		pq.push(make_pair(-dist[i], i));
	}
	while (!pq.empty()) {
		p = pq.top();
		pq.pop();
		if (!check[p.second]) {
			check[p.second] = true;
			for (int i = 2; i <= N; i++) {
				if (check[i] || p.second == i) continue;
				if (dist[i] > dist[p.second] + conn[p.second][i]) {
					dist[i] = dist[p.second] + conn[p.second][i];
					pq.push(make_pair(-dist[i], i));
				}
			}
		}
	}
}

 

그런 다음 dist배열을 토대로 문제 조건 1.에서 말한 조건을 비교하면서 스패닝트리를 만든다. 

 

최소 스패닝 트리 + 문제 조건

 

최소 스패닝 트리와 다익스트라는 모드 priority_queue를 사용했다. 

 

#include<iostream>
#include<queue>
#include<utility>
using namespace std;
#define MAX 1001

priority_queue<pair<int, int> > pq;
priority_queue<pair<int, pair<int, int> > > sol_pq;
queue<pair<int, int> > answer;
pair<int, int> p;
pair<int, pair<int, int> > p2;
int N, M, a, b, c, INF = 987654321;
int visit_num = 0;
int conn[MAX][MAX] = { 0 };
int dist[MAX];
bool visited[MAX] = { false };
bool check[MAX] = { false };


void di_init() {
	for (int i = 2; i <= N; i++) {
		dist[i] = conn[1][i];
		pq.push(make_pair(-dist[i], i));
	}
	while (!pq.empty()) {
		p = pq.top();
		pq.pop();
		if (!check[p.second]) {
			check[p.second] = true;
			for (int i = 2; i <= N; i++) {
				if (check[i] || p.second == i) continue;
				if (dist[i] > dist[p.second] + conn[p.second][i]) {
					dist[i] = dist[p.second] + conn[p.second][i];
					pq.push(make_pair(-dist[i], i));
				}
			}
		}
	}

}

void solve() {
	for (int i = 2; i <= N; i++) {
		sol_pq.push(make_pair(-conn[1][i], make_pair(1, i)));
	}
	while (!sol_pq.empty()) {
		p2 = sol_pq.top();
		sol_pq.pop();
		p2.first = -p2.first;
		if (visited[p2.second.second] || dist[p2.second.second] < dist[p2.second.first] + p2.first) continue;
		visited[p2.second.second] = true;
		visit_num++;
		answer.push(make_pair(p2.second.first, p2.second.second));
		for (int i = 2; i <= N; i++) {
			if (visited[i]) continue;
			sol_pq.push(make_pair(-conn[p2.second.second][i], make_pair(p2.second.second, i)));
		}
	}

}

int main() {
	cin >> N >> M;

	for (int i = 0; i < M; i++) {
		cin >> a >> b >> c;
		conn[a][b] = c;
		conn[b][a] = c;
	}

	for (int i = 1; i <= N; i++) {
		for (int j = 1; j <= N; j++) {
			if (i == j) continue;
			if (conn[i][j] == 0) conn[i][j] = INF;
		}
	}

	di_init();
	solve();
	cout << visit_num << "\n";
	while (!answer.empty()) {
		p = answer.front();
		answer.pop();
		cout << p.first << " " << p.second << "\n";
	}

}