포스트

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 라이센스를 따릅니다.