포스트

PS 일기 2024년 1월 31일 (2일 차) (1376번 민식우선탐색)

PS 일기 2024년 1월 31일 (2일 차) (1376번 민식우선탐색)

PS 일기 2024년 1월 31일 (2일 차)

PS 일기란?

PS 실력 향상을 목적으로 날마다 푼 문제를 회고하는 것입니다. 문제의 간단한 해법과 함께 어떤 방식으로 접근했는지, 그리고 문제의 해법을 찾는 데 결정적이었던 깨달음은 무엇이었는지 등을 기록합니다.

오늘의 문제

1376번 민식우선탐색

알고리즘 분류

  • Segment Tree
  • Binary Search
  • DFS

문제 설명

그래프가 주어지고 1번 정점을 시작으로 민식우선탐색의 결과를 출력합니다. 민식우선탐색은 다음과 같습니다.

  1. 기본적으로 DFS와 유사합니다. 다만 방문하는 순서가 다릅니다.
  2. 방문할 수 있는 정점의 개수가 홀수이면 그 정점 번호의 중간값인 정점에 방문합니다.
  3. 방문할 수 있는 정점의 개수가 짝수이면 가장 작은 정점번호의 정점으로 방문합니다.

문제 풀이

관측 1

세그먼트 트리를 이용하여 중간값의 정점 번호, 가장 작은 정점 번호를 빠르게 구할 수 있습니다.

세그먼트 트리를 이용하면 중간값, 가장 작은 값을 빠르게 구할 수 있습니다. 물론 이분탐색을 이용하여 빠르게 구할 수 도 있겠지만 방문한 정점을 지우려면 이분탐색은 느립니다. 펜윅트리를 사용해도 좋습니다. 또 BST로도 될 수 있을 것 같습니다.

관측 2

문제는 사이클입니다.

정점 $u$와 직접 연결되어 있는 정점을 $v$, $w$라 했을 때 $u$에서 탐색을 시작하여 $v$에 방문하면 $u$에서 $w$의 방문 여부를 알아야 탐색을 정상적으로 할 수 있습니다. 이는 $v$와 $w$가 직접 또는 간접적으로 연결되어 있을 수 있기 때문입니다. 하지만 이 경우를 잘 살펴보면 사이클이 존재하는 경우인 것을 알 수 있습니다. 따라서 탐색하며 직접 연결된 정점 중 이미 방문을 한 정점이 있다면 그 방문한 정점에 탐색 중인 정점을 방문하였다고 표시하면 됩니다.

구현

모든 노드에 대하여 $n$만큼의 크기로 세그먼트 트리 만든다면 메모리 초과가 날것이 뻔하기 때문에 각 노드에 대하여 연결의 개수만큼만 만들고, 연결의 저장 순서를 정점 번호로 정렬한 후 이분 탐색을 하여 세그먼트 트리에서 특정 정점에 해당하는 위치를 찾았습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include <bits/stdc++.h>

using namespace std;

const int MAXN = 100005;

int n, m;
bool visited[MAXN];
vector<int> graph[MAXN];
vector<int> trees[MAXN];

void update(int tree, int start, int end, int index, int pos, int value)
{
    if(end < pos || pos < start) return;

    if(start == end) {
        trees[tree][index] = value;
        return;
    }

    int mid = (start + end) / 2;
    update(tree, start, mid, 2*index, pos, value);
    update(tree, mid+1, end, 2*index+1, pos, value);
    trees[tree][index] = trees[tree][2*index] + trees[tree][2*index+1];
}

int kth(int tree, int start, int end, int index, int k)
{
    if(start == end) return start;
    
    int mid = (start + end) / 2;

    if(k <= trees[tree][2*index]) {
        return kth(tree, start, mid, 2*index, k);
    } else {
        return kth(tree, mid+1, end, 2*index+1, k - trees[tree][2*index]);
    }
}

void mFS(int node)
{
    cout << node << " ";
    visited[node] = true;
    trees[node].resize(4*graph[node].size());
    for(int i = 0; i < graph[node].size(); i++) {
        if(visited[graph[node][i]]) {
            int pos = lower_bound(graph[graph[node][i]].begin(), graph[graph[node][i]].end(), node) - graph[graph[node][i]].begin();
            update(graph[node][i], 0, graph[graph[node][i]].size() - 1, 1, pos, 0);
        } else {
            update(node, 0, graph[node].size() - 1, 1, i, 1);
        }
    }

    while(0 < trees[node][1]) {
        int k = (trees[node][1] & 1) ? trees[node][1] / 2 + 1 : 1;
        int next = graph[node][kth(node, 0, graph[node].size() - 1, 1, k)];
        mFS(next);
    }
}

int main()
{
    ios_base::sync_with_stdio(false); cin.tie(NULL);
    
    cin >> n >> m;
    for(int i = 0; i < m; i++) {
        int u, v;
        cin >> u >> v;
        if(u == v) continue;
        graph[u].push_back(v);
        graph[v].push_back(u);
    }

    for(int i = 1; i <= n; i++) {
        sort(graph[i].begin(), graph[i].end());
        graph[i].erase(unique(graph[i].begin(), graph[i].end()), graph[i].end());
    }


    mFS(1);
}
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.