본문 바로가기

Problem Solving/문제풀이

백준 5829번 Luxury River Cruise

반응형

USACO 2013 Open Contest Silver 2번 문제이다.

문제 요약

정점이 N개 주어지고, N개의 정점은 각각 두 개의 간선을 갖는다. 정점의 각 간선은 Left와 Right로 나타내어진다. 길이 M의 Left 혹은 Right로 이루어진 순열이 주어지는데, 1번 정점에서 출발하여 M개의 순열을 K번 반복했을 때 도착하는 정점의 번호를 출력하는 것이 문제이다.

문제의 제한은 $ 1 \le N \le 1000, 1 \le M \le 500, 1 \le K \le 10^9$이다.

풀이

당연히 그래프를 만들고 $ MK $번 간선을 타고 지나가면 시간 초과다. 일단 이걸 줄여보자. 길이 $M$짜리 순열을 $K$번 타고 지나간 뒤의 결과를 찾는 것인데, 하나의 정점에서 간선을 $M$번 타고 가다가 그 중간에서 끝나는 경우는 존재하지 않는다. 그리고, 그래프는 중간에 변하지 않으므로 한 정점에서 간선을 $M$번 타고 지나간 결과는 절대 바뀌지 않는다.

이를 이용해서 각 정점에서 간선을 $M$번 타고 지나간 결과를 계산해주고 도착점과 시작점을 이어주어 새로운 그래프를 만들자. 예제로 주어진 그래프라면 1->2, 2->3, 3->4, 4->1과 같은 간선을 가지는 그래프로 변한다. 이제 이 그래프에서 1번부터 출발해서 간선을 $K$번 타면 답을 알 수 있다.

그런데 $K$가 $10^9$로 일일이 다 돌기엔 너무 크다. 새로 만든 그래프를 잘 살펴보자. 이 그래프는 정점이 $N$개면서 각 정점의 차수가 1인 유향 그래프이다. 그리고 원래 여러개의 컴포넌트들을 축약 시켜놓은 것이다. 이걸 잘 생각해보면, 그래프는 사이클을 가질 수 밖에 없다는 사실을 알 수 있다.

이를 이용해서 1번부터 출발해서 사이클의 시작점과 사이클의 길이를 찾아내면 답을 구할 수 있다.

시간복잡도를 계산해보자. 먼저 새로운 그래프를 만드는 데에 각 정점마다 간선을 $M$번씩 타고 지나가기 때문에 $O(NM)$.

새로 만들어진 그래프에서 1번 정점이 속해있는 사이클을 찾는데, 이 사이클의 길이는 길어봤자 $N$이기 때문에 $O(N)$이 걸린다.

따라서 총 시간복잡도는 $O(NM+N)$으로 아주 빠르게 끝낼 수 있다.

아래는 코드다. 항상 까먹기 쉬운 사실이지만, 이전 숫자에 따라 어떤 일정한 규칙에 맞춰 생성되는 수열은 꼭 수열의 시작부터 사이클이 생성되는 것이 아니라 중간부터 시작할 수도 있다는 것이다. $ \rho $문자 처럼 말이다.

#include<bits/stdc++.h>
using namespace std;
using ll = long long;

ll N, M, K;
int adj[1005][2];
bool vis[1005];
vector<int> path;
vector<int> G(1005);

void dfs(int start, int cur, int idx) {
    if(idx == M) {
        G[start] = cur; return ;
    }
    int nxt = adj[cur][path[idx]];
    dfs(start, nxt, idx+1);
}

int main() {
    cin.tie(nullptr); ios::sync_with_stdio(false);
    cin >> N >> M >> K;
    for(int i=1;i<=N;++i) {
        int l, r;
        cin >> l >> r;
        adj[i][0] = l; adj[i][1] = r;
    }
    for(int i=0;i<M;++i) {
        string s;
        cin >> s;
        if(s[0] == 'L') path.push_back(0);
        else path.push_back(1);
    }
    for(int i=1;i<=N;++i) dfs(i,i,0);
    int cur = 1;
    int len = 0;
    int start;
    for(int i=0;i<=N;++i) {
        if(vis[cur]) {
            start = cur; len = i; break;
        }
        vis[cur] = true;
        cur = G[cur];
    }
    cur = 1;
    for(int i=0;i<=N;++i) {
        if(cur == start) {
            len -= i;
            break;
        }
        cur = G[cur];
        --K;
        if(K == 0) break;
    }
    if(K == 0) {
        cout << cur << '\n';
        return 0;
    }
    K %= len;
    cur = start;
    while(K--) cur = G[cur];
    cout << cur << '\n';
    return 0;
}
반응형

'Problem Solving > 문제풀이' 카테고리의 다른 글

백준 15576번 큰 수 곱셈 (2)  (0) 2021.02.04
백준 11714번 Midpoint  (0) 2021.01.30
백준 19228번 Key storage  (0) 2021.01.29
백준 17415번 Huge Integer!  (0) 2021.01.29
백준 2844번 자료구조  (0) 2021.01.29