그래프의 오일러 서킷과 관련된 문제였다. 순환을 이루는 정점들은 서로 팀을 이루고 있다고 볼 수 있고, 순환하지 않는 정점들의 개수를 찾아주면 된다.
#include <iostream>
#include <cstring>
#include <queue>
#include <vector>
using namespace std;
int n;
int adj[100001];
bool visited[100001];
int dfs(int here, vector<int>& circuit) {
visited[here] = true;
circuit.push_back(here);
int there = adj[here];
if (!visited[there]) {
return dfs(there, circuit);
}
return there;
}
int bfs(int start, vector<int>& circuit) {
queue<int> q;
q.push(start);
visited[start] = true;
while (!q.empty()) {
int here = q.front();
q.pop();
circuit.push_back(here);
int there = adj[here];
if (!visited[there]) {
visited[there] = true;
q.push(there);
}
else
return there;
}
}
int main(void) {
cin.tie(0);
ios_base::sync_with_stdio(0);
int tc;
cin >> tc;
while (tc--) {
memset(visited, false, sizeof(visited));
cin >> n;
for (int i = 1; i <= n; i++)
cin >> adj[i];
int result = 0;
for (int i = 1; i <= n; i++) {
if (!visited[i]) {
vector<int> circuit;
int idx = bfs(i, circuit);
int len = circuit.size();
for (int i = 0; i < len; i++) {
if (idx == circuit[i]) break;
result++;
}
}
}
cout << result << endl;
}
return 0;
}
처음 풀었을 땐 플로이드-와샬 알고리즘으로 구현해서 정답을 받았는데, 생각해보니 N이 최대 1000이기 때문에 시간복잡도가 O(N^3)인 플로이드-와샬 알고리즘을 사용하면 최악의 경우 10억만큼의 시간이 걸리게 된다. 입력데이터가 부실했기 때문에 운좋게 통과했다고 생각한다.
따라서 다익스트라 알고리즘을 사용해서 다시 구현하였다.
//추가 : 질문게시판에서 시간복잡도를 더 줄이는 방법에 대한 힌트를 발견하였다.
도착지점이 X로 정해져 있기 때문에 모든 정점의 최단 경로를 다 구할 필요 없이 정점으로 들어오는 간선들의 집합과 정점에서 나가는 간선들의 집합을 분리한 다음, 정점 X에서 들어오는 간선과 나가는 간선을 대상으로 두번의 다익스트라를 수행하면 문제를 해결할 수 있다.
// 플로이드-와샬 알고리즘 사용
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int INF = 987654321;
vector<vector<int>> adj(1001, vector<int>(1001, INF));
void floyd(int N) {
for (int i = 1; i <= N; i++) {
adj[i][i] = 0;
}
for (int k = 1; k <= N; k++)
for (int i = 1; i <= N; i++)
for (int j = 1; j <= N; j++)
adj[i][j] = min(adj[i][j], adj[i][k] + adj[k][j]);
}
int main(void) {
int N, M, X;
cin >> N >> M >> X;
for (int i = 0; i < M; i++) {
int A, B, W;
cin >> A >> B >> W;
adj[A][B] = W;
}
floyd(N);
int result = 0;
for (int i = 1; i <= N; i++) {
result = max(result, adj[i][X] + adj[X][i]);
}
cout << result << endl;
return 0;
}
// 다익스트라 알고리즘 사용
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
const int INF = 987654321;
int N, M, X;
vector<pair<int, int>> adj[1001];
vector<vector<int>> allTownDist(1001);
void dijkstra(int start) {
vector<int> dist(N+1, INF);
dist[start] = 0;
priority_queue<pair<int, int>> pq;
pq.push({ 0, start });
while (!pq.empty()) {
int here = pq.top().second;
int cost = -pq.top().first;
pq.pop();
if (dist[here] < cost) continue;
for (int i = 0; i < adj[here].size(); i++) {
int there = adj[here][i].first;
int nextCost = adj[here][i].second + cost;
if (dist[there] > nextCost) {
dist[there] = nextCost;
pq.push({ -nextCost, there });
}
}
}
allTownDist[start] = dist;
}
int main(void) {
cin >> N >> M >> X;
for (int i = 0; i < M; i++) {
int A, B, W;
cin >> A >> B >> W;
adj[A].push_back({ B, W });
}
for (int i = 1; i <= N; i++)
dijkstra(i);
int result = 0;
for (int i = 1; i <= N; i++)
result = max(result, allTownDist[i][X] + allTownDist[X][i]);
cout << result << endl;
return 0;
}
// 다익스트라 2번으로 문제를 해결하는 코드
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
const int INF = 987654321;
int N, M, X;
vector<pair<int, int>> adjIn[1001];
vector<pair<int, int>> adjOut[1001];
vector<int> inDist(1001);
vector<int> outDist(1001);
void dijkstra(int start, int type) {
vector<int> dist(N + 1, INF);
dist[start] = 0;
priority_queue<pair<int, int>> pq;
pq.push({ 0, start });
if (type == 1) {
while (!pq.empty()) {
int here = pq.top().second;
int cost = -pq.top().first;
pq.pop();
if (dist[here] < cost) continue;
for (int i = 0; i < adjIn[here].size(); i++) {
int there = adjIn[here][i].first;
int nextCost = adjIn[here][i].second + cost;
if (dist[there] > nextCost) {
dist[there] = nextCost;
pq.push({ -nextCost, there });
}
}
}
inDist = dist;
}
else {
while (!pq.empty()) {
int here = pq.top().second;
int cost = -pq.top().first;
pq.pop();
if (dist[here] < cost) continue;
for (int i = 0; i < adjOut[here].size(); i++) {
int there = adjOut[here][i].first;
int nextCost = adjOut[here][i].second + cost;
if (dist[there] > nextCost) {
dist[there] = nextCost;
pq.push({ -nextCost, there });
}
}
}
outDist = dist;
}
}
int main(void) {
cin >> N >> M >> X;
for (int i = 0; i < M; i++) {
int A, B, W;
cin >> A >> B >> W;
adjIn[A].push_back({ B, W });
adjOut[B].push_back({ A, W });
}
dijkstra(X, 1); // 정점으로 들어오는 간선들의 최단경로
dijkstra(X, 2); // 정점에서 나가는 간선들의 최단경로
int result = 0;
for (int i = 1; i <= N; i++)
result = max(result, inDist[i] + outDist[i]);
cout << result << endl;
return 0;
}
한 정점에서 이동할 수 있는 경우는 U버튼을 눌렀을 때와 D버튼을 눌렀을 때 두가지 경우이다.
따라서 두가지 경우를 큐에 담아주면서 현재 위치가 도착지점 G와 같아지는 순간의 이동 횟수를 출력하면 된다.
만약 가능한 모든 층을 다 탐색했는데 도착지점 G를 방문하지 못했다면 불가능하므로 "use the stairs"를 출력한다.
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
const int INF = 987654321;
int F, S, G, U, D;
int visited[1000001];
int bfs() {
queue<pair<int, int>> q;
q.push({ S, 0 });
while (!q.empty()) {
int here = q.front().first;
int press = q.front().second;
q.pop();
if (here == G) return press;
int thereUp = here + U;
if (thereUp <= F && !visited[thereUp]) {
visited[thereUp] = true;
q.push({ thereUp, press + 1 });
}
int thereDown = here - D;
if (thereDown > 0 && !visited[thereDown]) {
visited[thereDown] = true;
q.push({ thereDown, press + 1 });
}
}
return INF;
}
int main(void) {
cin >> F >> S >> G >> U >> D;
int result = bfs();
if (result == INF)
cout << "use the stairs" << endl;
else
cout << result << endl;
return 0;
}
먼저 입력으로 주어지는 건설 순서대로 그래프를 만든다. 필자는 역순으로 탐색할 계획이었기 때문에 X, Y 를 입력받았으면 Y정점에서 X정점으로 가는 간선을 연결하였다.
그래프를 연결했으면 승리를 위한 건물을 시작정점으로 하는 깊이 우선 탐색을 수행하는데, 현재 정점의 건물까지 짓는데 걸리는 시간은 (현재 정점에서 건물을 짓는 시간 + 현재 정점에서 갈 수 있는 정점의 건물을 짓는데 걸리는 시간들 중 가장 큰 값) 이므로 이를 그대로 구현해주면 된다.
추가로 이미 계산한 정점을 다시 호출하는 경우가 많으면 시간복잡도가 커지게 되므로 동적 계획법의 메모이제이션 기법을 통해 한번 계산한 정점의 최소 시간을 저장하여 중복되는 연산을 최소화해야 한다.
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
int N, K;
vector<int> adj[1001];
vector<int> weight(1001);
int memo[1001];
int dfs(int start) {
int& ret = memo[start];
if (ret != -1) return ret;
ret = 0;
for (int i = 0; i < adj[start].size(); i++) {
int next = adj[start][i];
ret = max(ret, dfs(next));
}
ret += weight[start];
return ret;
}
int main(void) {
int tc;
cin >> tc;
while (tc--) {
memset(memo, -1, sizeof(memo));
for (int i = 0; i < 1001; i++)
adj[i].clear();
cin >> N >> K;
for (int i = 1; i <= N; i++)
cin >> weight[i];
for (int i = 0; i < K; i++) {
int a, b;
cin >> a >> b;
adj[b].push_back(a);
}
int W;
cin >> W;
cout << dfs(W) << endl;
}
return 0;
}