알고리즘/문제 풀이

1865번: 웜홀

Themion 2021. 12. 10. 10:30

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

 

1865번: 웜홀

첫 번째 줄에는 테스트케이스의 개수 TC(1 ≤ TC ≤ 5)가 주어진다. 그리고 두 번째 줄부터 TC개의 테스트케이스가 차례로 주어지는데 각 테스트케이스의 첫 번째 줄에는 지점의 수 N(1 ≤ N ≤ 500),

www.acmicpc.net

음의 가중치가 존재하는 그래프에서 음의 가중치를 갖는 사이클을 탐지하는 문제이므로, 벨만-포드 알고리즘을 사용해 해결할 수 있다. 노드 i와 노드 j에 대해, 두 노드 사이의 에지의 개수가 한 개보다 많을 수 있다는 점과, i와 j가 같은 경우가 나올 수 있다는 점에 주의하자.

#include <iostream>
#include <queue>
#include <vector>

using namespace std;

typedef pair<int, int> edge;

#define MAX_N 500
#define INF 0x3f3f3f3f

#define _cost first
#define _node second

queue<int> q;
vector<edge> v[MAX_N];

int N, M, W, cost[MAX_N]; 
int graph[MAX_N][MAX_N];

void set_min(int &a, int b) { a = a < b ? a : b; }

// node에서 에지를 하나 사용해 그래프를 탐색
bool push(int node) {
    // 비용을 하나라도 단축시켰다면 true
    bool val_changed = false;
    // 시작 노드에서 node까지 이동한 뒤 에지를 하나 사용한 비용
    int cost_;

    // node가 q.front이므로 q에서 node를 pop한다
    q.pop();

    // node에서 시작하는 모든 에지에 대해
    for (edge e : v[node]) {
        // 현재 에지를 사용해 얻을 수 있는 비용을 계산
        cost_ = cost[node] + e._cost;
        // 비용 감소가 불가능하다면 현재 에지를 건너뛴다
        if (cost[e._node] <= cost_) continue;

        // 비용을 감소시킨 뒤 현재 에지를 사용해 도착한 노드를 q에 push
        cost[e._node] = cost_;
        q.push(e._node);
        // 비용의 변화가 발생했으므로 val_changed에 true를 저장
        val_changed = true;
    }

    // 값이 바뀌었는지 여부를 반환
    return val_changed;
}

// 그래프가 음의 가중치를 갖는다면 true
bool bellman_ford() {
    // 에지를 i개 사용할 때의 q의 크기
    int size = q.size();

    // 에지를 1개부터 N - 1개까지 사용할 때의 최소 비용을 계산
    for (int i = 1; i < N; i++) {
        // 에지를 i - 1개 사용했을 때 도달한 노드에서 에지를 하나 사용해 이동
        while (size--) push(q.front());
        // q의 크기를 재측정
        size = q.size();
    }

    // 에지를 총 N - 1개 사용한 이후에 갱신 가능한 거리가 있다면
    // 음의 가중치를 갖는 사이클이 존재하므로 true를 반환
    while (size--) if (push(q.front())) return true;
    // 거리를 갱신하지 못했다면 음의 가중치를 갖는 사이클이 존재하지 않는다
    return false;
}

bool test_case() {
    // S, E, T: 노드 S에서 노드 E로 향하며 가중치 T를 가진 에지
    int S, E, T;
    
    // 그래프의 크기와 도로, 웜홀의 수를 입력받은 뒤
    cin >> N >> M >> W;

    // 전역변수 초기화
    for (int i = 0; i < N; i++)  {
        fill_n(graph[i], N, INF);
        v[i] = vector<edge>();
    }
    fill_n(cost, N, INF);
    q = queue<int>();

    // 각 에지와 웜홀에 대해
    for (int i = 0; i < M + W; i++) {
        // 에지를 입력받은 뒤
        cin >> S >> E >> T;

        // 에지가 웜홀이라면 가중치를 음수로 변경
        if (i >= M) T = -T;
        // 에지의 최솟값을 설정한 뒤
        set_min(graph[--S][--E], T);
        // 에지가 도로라면 에지를 뒤집어서 최솟값을 설정
        if (i < M) set_min(graph[E][S], T);

        // 모든 에지의 시작점을 탐색의 시작점으로 설정
        q.push(S);
    }

    // 노드 i와 노드 j를 잇는 에지를 v에 push
    for (int i = 0; i < N; i++) for (int j = 0; j < N; j++)
        if (graph[i][j] != INF) v[i].push_back({graph[i][j], j});
    
    // 벨만-포드 알고리즘으로 음의 가중치를 갖는 사이클을 탐색
    return bellman_ford();
}

int main() {
    // 입출력 속도 향상
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    int TC;
    cin >> TC;
    // 테스트 케이스의 수만큼 반복
    while (TC--) cout << (test_case() ? "YES" : "NO") << '\n';

    return 0;
}

'알고리즘 > 문제 풀이' 카테고리의 다른 글

1874번: 스택 수열  (0) 2021.12.10
1871번: 좋은 자동차 번호판  (0) 2021.12.10
1850번: 최대공약수  (0) 2021.12.10
1806번: 부분합  (0) 2021.12.10
1786번: 찾기  (0) 2021.12.09