9655. 돌 게임


문제

돌 게임은 두 명이서 즐기는 재밌는 게임이다.

탁자 위에 돌 N개가 있다. 상근이와 창영이는 턴을 번갈아가면서 돌을 가져가며, 돌은 1개 또는 3개 가져갈 수 있다. 마지막 돌을 가져가는 사람이 게임을 이기게 된다.

두 사람이 완벽하게 게임을 했을 때, 이기는 사람을 구하는 프로그램을 작성하시오. 게임은 상근이가 먼저 시작한다.

입력

첫째 줄에 N이 주어진다. (1 ≤ N ≤ 1000)

출력

상근이가 게임을 이기면 SK를, 창영이가 게임을 이기면 CY을 출력한다.

예제 입력 

5

예제 출력 

SK



1. 접근


내가 상근이라면 현재 남은 돌의 갯수를 보고 어떤 선택을 내리는게 완벽한 플레이일까?


1개 남았다면 당연히 1개 가져가면 이긴다.


3개 남았다면 당연히 3개 가져가면 이긴다.


2개 남았다면 1개 가져가고 깨끗하게 지는게 매너플레이다.


4개 남았다면 무슨 짓을 해도 진다.


분명 남은 갯수와 승패에는 연관이 있는 것 같지만 일단 모든 경우를 생각해 보기로 하자.



2-1) 풀이 1


나나 상대나 두 가지 초이스를 할 수 있으므로 이차원 배열 dp[턴][남은 돌]을 정의해보자.


이 배열은 내가 이기는지 지는지 상태를 저장한다.


현재 dp[my][N] 이라면, 나는 1개나 3개를 가져간다. 턴은 상대에게 넘어가고, 그 경우에 내가 이길 수 있는 경우가

하나라도 있다면 나는 그 선택을 완벽하게 할 것이다.


반대로 현재 dp[your][N] 이라면 상대는 1개나 3개를 가져갈 수 있고, 턴은 내것이 될 것이고, 그 경우에 상대가 이길 수 있는 경우가

하나라도 있다면 상대는 그 선택을 완벽하게 할 것이다.


같은 말로, 내가 모두 이기는 경우만 남는다면 나의 승리다. 하나라도 상대가 이기는 경우가 있다면 상대의 승리다.



따라서 우리는 재귀적으로 경우를 확장시켜 보기로 하자.


dp[my][N] 부터 시작해서 dp[my][0], dp[your][0] 까지 확장시킨다.


한 케이스는 두 케이스로 분화된다. (1개를 가져가거나 3개를 가져가거나)


남은 돌이 0개인 채로 턴을 받으면  그 사람의 패배인 것이다.



2-1) 풀이 1. 코드


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
#include <stdio.h>
#include <string.h>
using namespace std;
 
#define my 1
#define your 0
#define win true
#define lose false
 
int n;
int dp[2][1001];
 
int func(int turn, int remain) {
    if (remain == 0) {
        if (turn == my)
            return dp[turn][remain] = lose;
        if (turn == your)
            return dp[turn][remain] = win;
    }
 
    if (remain < 0) {
        if (turn == my)
            return 1;
        if (turn == your)
            return 0;
    }
 
    if (dp[turn][remain] != -1)
        return dp[turn][remain];
 
    if (turn == my)
        dp[my][remain] =
        func(turn ^ 1, remain - 1| func(turn ^ 1, remain - 3);
 
    if (turn == your)
        dp[your][remain] =
        func(turn ^ 1, remain - 1& func(turn ^ 1, remain - 3);
 
    return dp[turn][remain];
}
 
int main() {
    scanf("%d"&n);
    memset(dp, -1sizeof(dp));
 
    if (func(1, n) == win)
        puts("SK");
    else
        puts("CY");
 
    return 0;
}
cs


분화시키다보면 remain이 음수가 되버리는 경우가 있다(21행). 그 경우엔 논리식(31, 35행) 에 영향을 주지않는 값을 리턴해주기로 하자.



2-2) 풀이 2


베스킨라벤스 31 게임을 생각해보면 비슷한 이유로 승패가 갈린다.


우리는 초딩때의 경험으로 30, 26, 22, ... , 2를 먹으면 이긴다는 필승법을 알고 있다.


30을 먹으면 상대는 31을 먹고 질 수 밖에 없다.

30을 먹으려면 26을 먹어야 한다. 상대가 몇개를 먹건 나는 30을 먹을 수 있다.

26을 먹으려면 22을 먹어야 한다.

...


이는 한 번에 3개까지 먹을 수 있다는 게임의 특징에서 비롯된다.



마찬가지로 상대가 이길 수 없게 할려면 2개나 4개를 남겨주면 이긴다.

2개를 남기려면 내 턴에 3개나 5개여야 한다.

4개를 남기려면 내 턴에 5개나 7개여야 한다.

...



따라서 승패는 짝수 홀수에 따라 갈린다.


2-2) 풀이 2. 코드


1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
using namespace std;
 
int n;
 
int main() {
    scanf("%d"&n);
 
    n % 2 == 1 ? puts("SK") : puts("CY");
 
    return 0;
}
cs



3. 후기


이미 기록된 dp배열에 접근할 땐 기존 값을 리턴해주기로 했다. (코드의 28줄)


헌데 이 조건을 함수의 맨 처음에 두었더니 이상한 값이 리턴되었다. 먼저 기저를 작성하고 나중에 기존값 여부를 확인하자.

'알고리즘 > Dynamic Programming 동적계획법' 카테고리의 다른 글

백준) 3032 승리(IVANA)  (0) 2017.08.23
백준) 11062 카드게임(Card Game)  (1) 2017.08.23
백준) 9657 돌 게임 3  (0) 2017.08.18
백준) 9656 돌 게임 2  (0) 2017.08.17
백준) 11066 파일 합치기  (6) 2017.07.09

1. 세그먼트-트리(구간 트리)란?


세그먼트-트리(구간 트리)란 말 그대로 세그먼트(구간)를 저장하는 트리 자료구조다.


이 자료구조의 기능은 임의의 포인트를 어떤 세그먼트가 포함하고 있는지 반환해줄 수 있다.


또한 다음과 같은 구조를 갖는다.



1) 완전 이진 트리 (마지막 레벨을 제외하고 모든 노드는 자식을 두 개 갖는다.)


2) 트리의 잎들은 기본 간격들에 대응된다. 따라서 가장 좌단의 잎은 가장 처음의 기본 간격에 해당한다.


3) 내부 노드들은 기본 간격을 합친 간격에 대응된다.

따라서 두 잎을 자식으로 하는 내부 노드는, 두 잎에 해당하는 기본 간격을 합친 간격에 대응된다.



위와 같은 규칙으로 우리는 다음 기능들을 유추해 낼 수 있다.


1) n개의 간격이 있다면, 최대 4n + 1 개의 기본 간격이 있을 수 있다. 기본 간격은 잎에 대응된다.

그래서 이진트리의 잎이 4n + 1개 이므로, 높이는 O(log n)이다. 따라서 사용하는 공간은 O(n * log n)이다.


2) 루트 노드부터 1번, 왼쪽 자식은 2번, 오른쪽 자식은 3번, ... 식으로 번호를 매긴다면 다음이 성립한다.

부모 노드가 k번 이라면 왼쪽 자식은 2 * k 번, 오른쪽 자식은 2 * k + 1 번이다.

또한 n개의 기본 간격이 있다면, 총 노드 수는 2 * n - 1 개다.





2. 세그먼트-트리의 활용분야


세그먼트-트리의 강점은 높이가 log n 이라는 특징에서 비롯된다.


두 가지 질의를 하는 문제가 있다고 하자. (질의는 N번 주어진다.)


1) [from, to] 범위의 정보를 모두 구해라.

2) 기본 간격 A의 정보(a)를 b로 변경해라.


단순히 1번 질의를 O(N)으로 수행할 수도 있다.(1번 질의를 받을 때 마다 매번 구한다)

그러면 총 시간 복잡도는 O(N * N)이 될 것이다.


미리 [from, to]를 구하고 저장해둔다면 1번 질의를 상수시간에 해낼 수 있다.

하지만 2번 질의가 주어지는 순간, 미리 구해둔 정보 중 A와 관련된 모든 정보를 수정해야 한다.

따라서 최악의 경우 시간 복잡도는 동일하다.



하지만 세그먼트 트리는 위와 같은 질의를 높이에 비례해서 해낼 수 있다.


만약 1번 질의로 [1, 6]가 주어졌다고 하자.

루트는 현재 [1, 8]을 알고 있지만, 질의와 관계업는 정보도 갖고 있다. 따라서 왼쪽과 오른쪽 자식에게 세부 정보를 알아내야 한다.


왼쪽 자식(2번 노드)은 [1, 4]를 안다. 이는 질의의 일부분이다.


오른쪽 자식(3번 노드)은 [5, 8]을 안다. 하지만 질의와 겹치지 않는 부분이 있다. 다시 왼쪽, 오른쪽 자식에게 세부 정보를 알아내자.


3번 노드의 왼쪽 자식(6번 노드)은 [5, 6]을 안다. 이는 질의의 일부분이다.

3번 노드의 오른쪽 자식(7번 노드)은 [7, 8]을 안다. 이는 질의와 상관없는 정보다.


따라서 2번 노드와 6번 노드를 합치면 질의에 답할 수 있다.



2번 질의 또한 위와 유사한 과정을 통해 부모 노드들을 갱신시켜가면서 수행된다.


1번 질의와 2번 질의 모두 루트부터 시작된 트리 탐색으로 높이에 비례한 수행시간을 가진다.


따라서 세그먼트-트리의 위와 같은 질의에 대한 수행시간은 O(log n)이다.




3. 문제에 적용


정보 탐색과 갱신은 루트부터 시작된다.


탐색은 노드의 번호와 기타 정보를 통해 아래방향으로 진행된다.


하지만 갱신은 갱신 노드에 도달한 이후 윗방향으로 이루어진다. 따라서 재귀적인 코드가 직관적이다.



3-1) 백준 : 2042 구간 합 구하기 https://www.acmicpc.net/problem/2042


코드 : 


3-2) 백준 : 10868 최소값 https://www.acmicpc.net/problem/10868


코드 : 


1944. 복제 로봇 


문제

세준이는 어느 날 획기적인 로봇을 한 개 개발하였다. 그 로봇은 복제 장치를 이용하면 자기 자신을 똑같은 로봇으로 원하는 개수만큼 복제시킬 수 있다. 세준이는 어느 날 이 로봇을 테스트하기 위하여 어떤 미로에 이 로봇을 풀어 놓았다. 이 로봇의 임무는 미로에 흩어진 열쇠들을 모두 찾는 것이다. 그리고 열쇠가 있는 곳들과 로봇이 출발하는 위치에 로봇이 복제할 수 있는 장치를 장착해 두었다.

N*N의 정사각형 미로(1≤N≤50)와 M개의 흩어진 열쇠(1≤M≤250)의 위치, 그리고 이 로봇의 시작 위치가 주어져 있을 때, 모든 열쇠를 찾으면서 로봇이 움직이는 횟수의 합을 최소로 하는 프로그램을 작성하여라. 로봇은 상하좌우 네 방향으로 움직이며, 로봇이 열쇠가 있는 위치에 도달했을 때 열쇠를 찾은 것으로 한다. (복제된 로봇이어도 상관없다) 하나의 칸에 동시에 여러 개의 로봇이 위치할 수 있으며, 로봇이 한 번 지나간 자리라도 다른 로봇 또는 자기 자신이 다시 지나갈 수 있다. 복제에는 시간이 들지 않으며, 로봇이 움직이는 횟수의 합은 분열된 로봇 각각이 움직인 횟수의 총 합을 말한다. 복제된 로봇이 열쇠를 모두 찾은 후 같은 위치로 모일 필요는 없다.

입력

첫째 줄에 미로의 크기 N(1<=N<=50)과 열쇠의 개수 M(1<=M<=250) 이 공백을 사이에 두고 주어진다. 그리고 둘째 줄부터 N+1째 줄까지 미로의 정보가 주어진다. 미로는 1과 0, 그리고 S와 K로 주어진다. 1은 미로의 벽을 의미하고, 0은 지나다닐 수 있는 길, S는 로봇이 출발하는 위치, K는 열쇠의 위치가 주어진다. S는 1개, K는 M개가 주어진다. S와 K에서만 복제를 할 수 있음에 유의한다.

출력

첫째 줄에 모든 로봇이 움직인 횟수의 총 합을 출력한다. 모든 열쇠를 찾는 것이 불가능한 경우 횟수 대신 첫째 줄에 -1을 출력하면 된다.

예제 입력 

5 2
11111
1S001
10001
1K1K1
11111

예제 출력 

6



1. 접근


S에서 시작해서 K로 가야한다. K에 도착하면 원하는 수만큼 자신을 복사할 수 있으며, 최종적으로 모든 K에 도착해야한다.

이 때 가장 적은 경로의 합을 구하는 문제다.


S와 K들 사이의 경로를 그래프처럼 그려보자.


예시 입력에는 2개의 K개 있으며 (K1, K2) S에서 K1으로 가는 비용은 2, K2는 4이다.


먼저 K1으로 가기로 해보고, 도착 한뒤 자아 분열을 통해 K2로 가는 비용은 4이다.


따라서 우리는 S와 K들 사이의 최소거리를 비용으로 하는 간선들로 이루어진 그래프로 문제를 이해할 수 있다.


그러면 문제에서 원하는 해답은 그래프의 어떤 모습이겠는가?


그래프를 최소신장트리화시켜 비용의 합을 원하는 문제임을 알 수 있다.



2. 풀이


최소거리를 먼저 구해서 간선화시키는 방법이 필요할 것이다. 이에 BFS를 차용한다.


매 S, K들 마다 BFS를 돌려서 간선들을 만들고 저장한다.


BFS시에 모든 K를 방문하지 못한다면 길이 없는 것이므로 -1을 출력한다.


그 후엔 디스조인트-셋을 활용한 크루스칼 알고리즘을 사용하면 된다.


키의 번호는 정해져 있지 않으므로, 각 좌표의 키 번호를 지정하고 저장할 배열이 필요할 것이다.


이후 저장된 간선들에 대해 정렬하고 크루스칼 알고리즘을 돌리자.


참고 : http://js1jj2sk3.tistory.com/22



3. 코드


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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#include <stdio.h>
#include <string.h>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
 
struct cd {
    int x, y;
};
cd start;
vector<cd> key_list;
 
struct edge {
    int weight, from, to;
};
bool cmp(const edge& e1, const edge& e2) {
    return e1.weight < e2.weight;
}
vector<edge> E;
 
int n, m, i, j, cx, cy, nx, ny, cnt, sum;
int key_cnt, key_num[51][51];
int dir[4][2= { {-1,0},{1,0},{0,-1},{0,1} };
int dist[51][51];
bool visit[51][51];
char map[51][51];
 
int parent[251];
int find(int x) {
    if (x == parent[x])
        return x;
    else
        return parent[x] = find(parent[x]);
}
void unite(int x, int y) {
    x = find(x);
    y = find(y);
    parent[x] = y;
}
 
int bfs(int x, int y, int mom) {
    memset(visit, 0sizeof(visit));
    memset(dist, 0sizeof(dist));
    key_cnt = 1;
 
    queue<cd> q;
    q.push({ x,y });
    visit[x][y] = true;
 
    while (!q.empty()) {
        cx = q.front().x;
        cy = q.front().y;
        q.pop();
 
        for (i = 0; i < 4++i) {
            nx = cx + dir[i][0];
            ny = cy + dir[i][1];
 
            if (nx < 0 || nx >= n || ny < 0 || ny >= n)
                continue;
            if (visit[nx][ny] || map[nx][ny] == '1')
                continue;
 
            dist[nx][ny] = dist[cx][cy] + 1;
            visit[nx][ny] = true;
 
            if (map[nx][ny] == 'K') {
                key_cnt++;
                E.push_back({ dist[nx][ny], mom, key_num[nx][ny] });
            }
 
            if (key_cnt == key_list.size())
                return key_cnt;
 
            q.push({ nx,ny });
        }
    }
    return key_cnt;
}
 
int main() {
    scanf("%d %d"&n, &m);
    for (i = 0; i < n; ++i) {
        scanf("%s", map[i]);
        for (j = 0; j < n; ++j) {
            if (map[i][j] == 'S') {
                start = { i,j };
                map[i][j] = 'K';
            }
            if (map[i][j] == 'K') {
                key_list.push_back({ i,j });
                key_num[i][j] = ++key_cnt;
            }
        }
    }
 
    for (auto key : key_list) {
        if (bfs(key.x, key.y, key_num[key.x][key.y]) != key_list.size()) {
            puts("-1");
            return 0;
        }
    }
 
    sort(E.begin(), E.end(), cmp);
 
    for (i = 0; i <= key_list.size(); ++i)
        parent[i] = i;
 
    i = 0;
    while (cnt != key_list.size() - 1) {
        cx = find(E[i].from);
        cy = find(E[i].to);
 
        if (cx != cy) {
            unite(cx, cy);
            sum += E[i].weight;
            cnt++;
        }
        i++;
    }
    printf("%d", sum);
    return 0;
}
cs


2887. 행성 터널


문제

때는 2040년, 이민혁은 우주에 자신만의 왕국을 만들었다. 왕국은 N개의 행성으로 이루어져 있다. 민혁이는 이 행성을 효율적으로 지배하기 위해서 행성을 연결하는 터널을 만드려고 한다.

행성은 3차원 좌표위의 한 점으로 생각하면 된다. 

두 행성 A(xA, yA, zA)와 B(xB, yB, zB)를 터널로 연결할 때 드는 비용은 min(|xA-xB|, |yA-yB|, |zA-zB|)이다.

민혁이는 터널을 총 N-1개 건설해서 모든 행성이 서로 연결되게 하려고 한다. 이 때, 모든 행성을 터널로 연결하는데 필요한 최소 비용을 구하는 프로그램을 작성하시오.

입력

첫째 줄에 행성의 개수 N이 주어진다. (1 ≤ N ≤ 100,000) 다음 N개 줄에는 각 행성의 x, y, z좌표가 주어진다. 좌표는 -109보다 크거나 같고, 109보다 작거나 같은 정수이다. 한 위치에 행성이 두 개 이상 있는 경우는 없다. 

출력

첫째 줄에 모든 행성을 터널로 연결하는데 필요한 최소 비용을 출력한다.

예제 입력 

5
11 -15 -15
14 -5 -15
-1 -1 -5
10 -4 -1
19 -4 19

예제 출력 

4


1. 접근


모든 터널을 연결하는데 필요한 최소비용을 구하는 최소신장트리 문제이다.


크루스칼 알고리즘을 사용하자.



2. 풀이


간선을 고르는게 문제다.


A터널과 B터널의 연결은 x, y, z 좌표 중에 차이가 가장 작은 축으로 이루어진다.


단순히 N^2 으로 모든 터널을 만들어 본 다음에 크루스칼을 돌려볼 수 있지만, 시간제한이 문제다.



그러면 다음으로 떠오르는 생각은 정렬을 통해 N * log n 으로 해결할 방법을 찾아보는 것이다.


정렬이후 i, i - 1 행성의 차이로 터널을 만들어 보자. 그러면 총 3 * (N - 1) 개의 터널이 생길 것이다.


이 후 크루스칼을 돌리면 된다.



왜 이 방법이 최소신장트리의 모든 간선을 놓치지 않는 걸까?


단순히 x 축만 사용한다고 생각해보자. ( x = 0, 3, 4, 8, 13, 14 ) 


정렬 이후에 이웃 행성들 끼리만 터널을 만든다면 바로 0~14 까지 일렬로 쭈욱 터널이 생긴다.

바로 최소신장트리를 만들 수 있다. 이 간선들 외에 다른 최소신장 트리를 만드는 방법은 없다.


따라서 축을 세 개 쓴다고 해도 이는 변하지 않는다.

다만 쓸데 없는 간선이 2 * (N - 1)개 추가될 뿐이다.


정답인 N - 1 개의 간선은 놓쳐지지 않고 포함된다.


참고 : http://js1jj2sk3.tistory.com/22



3. 코드


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
82
83
84
#include <stdio.h>
#include <math.h>
#include <algorithm>
#include <vector>
using namespace std;
 
struct planet {
    int x, y, z, num;
};
bool cmpx(const planet& p1, const planet& p2) {
    return p1.x > p2.x;
}
bool cmpy(const planet& p1, const planet& p2) {
    return p1.y > p2.y;
}
bool cmpz(const planet& p1, const planet& p2) {
    return p1.z > p2.z;
}
planet pl[100000];
 
struct edge {
    int weight, from, to;
};
bool operator<(const edge& e1, const edge& e2) {
    return e1.weight > e2.weight;
}
vector<edge> ed;
 
 
int parent[100000];
 
int find(int x) {
    if (x == parent[x])
        return x;
    else
        return parent[x] = find(parent[x]);
}
void unite(int x, int y) {
    x = find(x);
    y = find(y);
 
    parent[x] = y;
}
 
int n, i;
 
int main() {
    scanf("%d"&n);
    for (i = 0; i < n; ++i) {
        scanf("%d %d %d"&pl[i].x, &pl[i].y, &pl[i].z);
        pl[i].num = i;
        parent[i] = i;
    }
 
    sort(pl, pl + n, cmpx);
    for (i = 0; i < n - 1++i)
        ed.push_back({ abs(pl[i].x - pl[i + 1].x), pl[i].num, pl[i + 1].num });
 
    sort(pl, pl + n, cmpy);
    for (i = 0; i < n - 1++i)
        ed.push_back({ abs(pl[i].y - pl[i + 1].y), pl[i].num, pl[i + 1].num });
 
    sort(pl, pl + n, cmpz);
    for (i = 0; i < n - 1++i)
        ed.push_back({ abs(pl[i].z - pl[i + 1].z), pl[i].num, pl[i + 1].num });
 
    sort(ed.begin(), ed.end());
 
    int ans = 0;
    int cnt = 0;
    for (int i = ed.size() - 1; cnt != n - 1;--i) {
        if (find(ed[i].from) != find(ed[i].to)) {
            cnt++;
            ans += ed[i].weight;
 
            unite(ed[i].from, ed[i].to);
        }
        if (cnt == n - 1)
            break;
    }
    printf("%d", ans);
 
    return 0;
}
cs


9372. 행성 터널


문제

상근이는 겨울방학을 맞아 N개국을 여행하면서 자아를 찾기로 마음먹었다. 

하지만 상근이는 새로운 비행기를 무서워하기 때문에, 최대한 적은 종류의 비행기를 타고 국가들을 이동하려고 한다.

이번 방학 동안의 비행 스케줄이 주어졌을 때, 상근이가 가장 적은 종류의 비행기를 타고 모든 도시들을 여행할 수 있도록 도와주자.

상근이가 한 국가에서 다른 국가로 이동할 때 다른 국가를 거쳐 가도(심지어 이미 방문한 국가라도) 된다.

입력

첫 번째 줄에는 테스트 케이스의 수 T(T ≤ 100)가 주어지고,

각 테스트 케이스마다 다음과 같은 정보가 주어진다.

  • 첫 번째 줄에는 국가의 수 N(2 ≤ N ≤ 1 000)과 비행기의 종류 M(1 ≤ M ≤ 10 000) 가 주어진다.
  • 이후 M개의 줄에 a와 b 쌍들이 입력된다. (a와 b를 왕복하는 비행기가 있다는 것을 의미한다)
  • 주어지는 비행 스케줄은 항상 연결 그래프(Connected Graph)를 이룬다.

출력

테스트 케이스마다 한 줄을 출력한다.

  • 상근이가 모든 도시를 여행하기 위해 타야 하는 비행기 종류의 최소 개수를 출력한다.

예제 입력 

2
3 3
1 2
2 3
1 3
5 4
2 1
2 3
4 3
4 5

예제 출력 

2
4



1. 접근


번역이 좀 애매하게 되어있지만, 가장 적은 종류의 비행기란 결국 최소신장트리를 구하라는 뜻과 일맥상통한다.


원문에는 가장 기장을 적게 알게 되면서 모든 도시를 방문하는 기장 수를 출력하라고 되어있다.


비행기를 처음 타면 기장과 안면을 트게 되고, 다시 타도 알던 사이이므로 상관없다. 즉 간선을 가장 적게 포함하는 트리를 만들어야 한다.

 


2. 풀이


간선의 비용을 모두 같다고 해보자. 그러면 우리가 만들어야 하는 최소신장트리다.


허나 우리는 이미 최소신장트리의 간선 수를 알고 있다. n - 1 이므로 바로 출력해주자.


참고 : http://js1jj2sk3.tistory.com/22



3. 코드


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
using namespace std;
 
int t, v, e, a, b, i;
 
int main() {
    scanf("%d"&t);
    while (t--) {
        scanf("%d %d"&v, &e);
        for (i = 0; i < e; ++i) {
            scanf("%d %d"&a, &b);
        }
        printf("%d\n", v - 1);
    }
    return 0;
}
cs


1647. 도시 분할 계획


문제

동물원에서 막 탈출한 원숭이 한 마리가 세상구경을 하고 있다. 그러다가 평화로운 마을에 가게 되었는데, 그곳에서는 알 수 없는 일이 벌어지고 있었다.

마을은 N개의 집과 그 집들을 연결하는 M개의 길로 이루어져 있다. 길은 어느 방향으로든지 다닐 수 있는 편리한 길이다. 그리고 각 길마다 길을 유지하는데 드는 유지비가 있다.

마을의 이장은 마을을 두 개의 분리된 마을로 분할할 계획을 가지고 있다. 마을이 너무 커서 혼자서는 관리할 수 없기 때문이다. 마을을 분할할 때는 각 분리된 마을 안에 집들이 서로 연결되도록 분할해야 한다. 각 분리된 마을 안에 있는 임의의 두 집 사이에 경로가 항상 존재해야 한다는 뜻이다.

그렇게 마을의 이장은 계획을 세우다가 마을 안에 길이 너무 많다는 생각을 하게 되었다. 일단 분리된 두 마을 사이에 있는 길들은 필요가 없으므로 없앨 수 있다. 그리고 각 분리된 마을 안에서도 임의의 두 집 사이에 경로가 항상 존재하게 하면서 길을 더 없앨 수 있다. 마을의 이장은 위 조건을 만족하도록 길들을 모두 없애고 나머지 길의 유지비의 합을 최소로 하고 싶다. 이것을 구하는 프로그램을 작성하시오.

입력

첫째 줄에 집의 개수N, 길의 개수M이 주어진다. N은 2이상 100,000이하인 정수이고, M은 1이상 1,000,000이하인 정수이다. 그 다음 줄부터 M줄에 걸쳐 길의 정보가 A B C 세 개의 정수로 주어지는데 A번 집과 B번 집을 연결하는 길의 유지비가 C라는 뜻이다.

출력

첫째 줄에 없애고 남은 길 유지비의 합의 최소값을 출력한다.

예제 입력 

7 12
1 2 3
1 3 2
3 2 1
2 5 2
3 4 4
7 3 6
5 1 5
1 6 2
6 4 1
6 5 3
4 5 3
6 7 4

예제 출력 

8



1. 접근


마을 안에 길이 너무 많아서 최소신장트리로 만들려고 한다.


단 마을을 둘로 나누고자 한다. 즉, 트리를 두개로 나누려고 하는 것이다.


최소신장트리이니 일단 크루스칼 알고리즘을 쓰기로 한다.



2. 풀이


일단 마을을 최소신장트리로 만들어보자.


그러면 길은 N - 1개일 것이다. 이 트리에서 간선을 하나 제거해서 한 집을 똑 떼어놓아 보자.


그러면 기존의 트리는 N - 2개의 길로 이루어졌고, N - 1개를 포함하는 최소신장트리이다.


당연히 떨어진 집은 자체로도 마을이고 최소신장트리이다.


그러면 제거할 간선의 기준은 어떻게 되겠는가? 트리 중 가장 큰 간선이다.


이 간선은 또한 크루스칼 알고리즘의 가장 마지막에 합류한 간선이다.


따라서 길을 N - 2개 까지만 크루스칼 알고리즘을 진행해도 같은 결과를 얻을 수 있다.



마을을 두개로 나누는 방법은 여러가지이지만, 간선의 수 (N-2) 는 동일하다.


따라서 가장 큰 간선을 제거하는 위의 방법이 최소임은 보장된다.


참고 : http://js1jj2sk3.tistory.com/22



3. 코드


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
#include <stdio.h>
#include <algorithm>
using namespace std;
 
struct edge {
    int weight, from, to;
};
bool operator<(const edge& e1, const edge& e2) {
    return e1.weight < e2.weight;
}
edge E[1000001];
 
int v, e, a, b, c, i, cnt, sum;
int parent[100001];
 
int find(int x) {
    if (x == parent[x])
        return x;
    else
        return parent[x] = find(parent[x]);
}
 
void unite(int x, int y) {
    x = parent[x];
    y = parent[y];
    parent[x] = y;
}
 
int main() {
    scanf("%d %d"&v, &e);
    for (i = 0; i < e; ++i) {
        scanf("%d %d %d"&a, &b, &c);
        E[i] = { c,a,b };
    }
    for (i = 1; i <= v; ++i)
        parent[i] = i;
    sort(E, E + e);
 
    i = 0;
    while (cnt != v - 2) {
        a = E[i].from;
        b = E[i].to;
        if (find(a) != find(b)) {
            unite(a, b);
            cnt++;
            sum += E[i].weight;
        }
        i++;
    }
    printf("%d", sum);
    return 0;
}
cs


1922. 네트워크 연결


문제

도현이는 컴퓨터와 컴퓨터를 모두 연결하는 네트워크를 구축하려 한다. 하지만 아쉽게도 허브가 있지 않아 컴퓨터와 컴퓨터를 직접 연결하여야 한다. 그런데 모두가 자료를 공유하기 위해서는 모든 컴퓨터가 연결이 되어 있어야 한다. (a와 b가 연결이 되어 있다는 말은 a에서 b로의 경로가 존재한다는 것을 의미한다. a에서 b를 연결하는 선이 있고, b와 c를 연결하는 선이 있으면 a와 c는 연결이 되어 있다.)

그런데 이왕이면 컴퓨터를 연결하는 비용을 최소로 하여야 컴퓨터를 연결하는 비용 외에 다른 곳에 돈을 더 쓸 수 있을 것이다. 이제 각 컴퓨터를 연결하는데 필요한 비용이 주어졌을 때 모든 컴퓨터를 연결하는데 필요한 최소비용을 출력하라. 모든 컴퓨터를 연결할 수 없는 경우는 없다.

입력

첫째 줄에 컴퓨터의 수(1<=N<=1000)가 주어진다.

둘째 줄에는 연결할 수 있는 선의 수(1<=M<=100,000)가 주어진다.

셋째 줄부터 M+2번째 줄까지 총 M개의 줄에 각 컴퓨터를 연결하는데 드는 비용이 주어진다. 이 비용의 정보는 세 개의 정수로 주어지는데, 만약에 a b c 가 주어져 있다고 하면 a컴퓨터와 b컴퓨터를 연결하는데 비용이 c만큼 든다는 것을 의미한다.

출력

모든 컴퓨터를 연결하는데 필요한 최소비용을 첫째 줄에 출력한다.

예제 입력 

6
9
1 2 5
1 3 4
2 3 2
2 4 7
3 4 6
3 5 11
4 5 3
4 6 8
5 6 8

예제 출력 

23



1. 접근


최소신장트리를 구해야 한다.


http://js1jj2sk3.tistory.com/23와 동일하다.


크루스칼 알고리즘을 사용하자.



2. 풀이


사이클의 유무를 확인하는데 디스조인트-셋을 사용한다.


참고 : http://js1jj2sk3.tistory.com/22



3. 코드


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
#include <stdio.h>
#include <algorithm>
using namespace std;
 
struct edge {
    int weight, from, to;
};
bool operator<(const edge& e1, const edge& e2) {
    return e1.weight < e2.weight;
}
edge E[100001];
 
int v, e, a, b, c, i, cnt, sum;
int parent[1001];
 
int find(int x) {
    if (x == parent[x])
        return x;
    else
        return parent[x] = find(parent[x]);
}
 
void unite(int x, int y) {
    x = parent[x];
    y = parent[y];
    parent[x] = y;
}
 
int main() {
    scanf("%d %d"&v, &e);
    for (i = 0; i < e; ++i) {
        scanf("%d %d %d"&a, &b, &c);
        E[i] = { c,a,b };
    }
    for (i = 1; i <= v; ++i)
        parent[i] = i;
    sort(E, E + e);
 
    i = 0;
    while (cnt != v - 1) {
        a = E[i].from;
        b = E[i].to;
        if (find(a) != find(b)) {
            unite(a, b);
            cnt++;
            sum += E[i].weight;
        }
        i++;
    }
    printf("%d", sum);
    return 0;
}
cs


1197. 최소 스패닝 트리


문제

그래프가 주어졌을 때, 그 그래프의 최소 스패닝 트리를 구하는 프로그램을 작성하시오.

최소 스패닝 트리는, 주어진 그래프의 모든 정점들을 연결하는 부분 그래프 중에서 그 가중치의 합이 최소인 트리를 말한다.

입력

첫째 줄에 정점의 개수 V(1≤V≤10,000)와 간선의 개수 E(1≤E≤100,000)가 주어진다. 다음 E개의 줄에는 각 간선에 대한 정보를 나타내는 세 정수 A, B, C가 주어진다. 이는 A번 정점과 B번 정점이 가중치 C인 간선으로 연결되어 있다는 의미이다. C는 음수일 수도 있으며, 절대값이 1,000,000을 넘지 않는다.

출력

첫째 줄에 최소 스패닝 트리의 가중치를 출력한다.

예제 입력 

3 3
1 2 1
2 3 2
1 3 3

예제 출력 

3



1. 접근


대놓고 최소스패닝트리를 찾으라고 써있는 문제.


크루스칼 알고리즘을 쓰자.



2. 풀이


간선들을 가중치 순으로 정렬하자.

가장 작은 간선부터 보면서 추가시 사이클이 생기지 않는다면 추가해 준다.

사이클의 유무는 디스조인트-셋 으로 확인해준다. 루트 노드가 같다면 연결되어 있다는 뜻이고, 추가하면 사이클이 생겨버린다.


가중치의 합만 구하면 되므로, 간선의 순서를 기억하지 않아도 좋다.


다음을 참고 : http://js1jj2sk3.tistory.com/22


3. 코드


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
#include <stdio.h>
#include <algorithm>
using namespace std;
 
int v, e, a, b, c, i, cnt, sum;
struct edge {
    int weight, from, to;
};
bool operator<(const edge& e1, const edge& e2) {
    return e1.weight < e2.weight;
}
edge E[100001];
 
int parent[100001];
 
int find(int x) {
    if (x == parent[x])
        return x;
    else
        return parent[x] = find(parent[x]);
}
 
void unite(int x, int y) {
    x = parent[x];
    y = parent[y];
    parent[x] = y;
}
 
int main() {
    scanf("%d %d"&v, &e);
    for (i = 0; i < e; ++i) {
        scanf("%d %d %d"&a, &b, &c);
        E[i] = { c,a,b };
    }
    for (i = 1; i <= v; ++i)
        parent[i] = i;
    sort(E, E + e);
 
    i = 0;
    while (cnt != v - 1) {
        a = E[i].from;
        b = E[i].to;
        if (find(a) != find(b)) {
            unite(a, b);
            cnt++;
            sum += E[i].weight;
        }
        i++;
    }
    printf("%d", sum);
    return 0;
}
cs

4

4. 후기


구조체를 algorithm : sort에 적용시키는 방법을 기억해두자. 연산자 오버로딩이 필요하다.

1. 최소신장트리란?


트리는 그래프의 노드들과 모든 노드간의 관계를 나타내는 간선들로 이루어진다.


최소신장트리란, 당연하게도 신장트리들 중에 가장 작은 규모의 트리를 말한다.


신장트리란 주어진 그래프의 모든 노드를 포함하면서, 모든 노드간에 관계가 있는(경로가 있는) 트리를 말한다.

다만 사이클은 포함하지 않아야 한다. 


규모라는 의미는, 간선들의 크기와 수에 비례하는 척도다.


따라서 최소신장트리는 신장트리를 만드는 여러 경우들 가운데 간선들의 크기(비용)의 합이 가장 작은 경우를 말한다.(사이클 미포함)




2. 최소신장트리를 구하는 알고리즘


그렇다면 최소신장트리를 어떻게 구할 수 있을까?


먼저 대표적인 알고리즘으로 크루스칼 알고리즘이 있다.


크루스칼 알고리즘의 흐름은 다음과 같다.


1) 간선들을 비용이 작은 순으로 정렬한다.

2) 가장 작은 간선부터 신장트리로 포함시킨다.(단 포함시키면 사이클이 생겨버리는 간선은 포함시키지 않는다.)

3) 모든 정점이 연결되지 않았다면 2단계로 돌아간다.


3단계로 보아 모든 정점이 포함될 것은 자명하고, 2단계의 제약조건으로 보아 사이클은 생기지 않을 것이다.

따라서 결과물이 신장트리가 될 것은 분명하다. 하지만 그 신장트리는 가장 작은 신장트리인가?


크루스칼 알고리즘이 최소신장트리를 찾는다는 증명은 다음과 같다.


우리는 다음을 귀납적으로 증명할 것이다 :

"알고리즘의 2단계를 수행하고 난 뒤 골라진 간선들의 집합을 F라고 하면, 항상 F를 포함하는 최소신장트리가 있다"


1단계에선 F는 빈 집합이다. 당연히 F를 포함하는 신장트리가 있다.

2단계는 반복적으로 수행된다. 다음에 2단계가 수행되면서 간선 e가 포함된다.

(F+e) 이 집합을 포함하는 최소신장트리가 있음을 보여야 한다.

e는 정답인 최소신장트리의 간선일 수도, 아닐 수도 있다. 맞다면 잘한 것이나, 아닐 경우 문제가 있다.


e가 노드 a, b를 잇는 간선이라고 해보자. 또한 원래 정답인 최소신장트리 또한 a에서 b로가는 경로를 유일하게 갖고 있다.

헌데 e를 추가함으로써 (F+e)는 사이클을 갖게된다. 이는 규칙에 어긋나므로 e가 정답의 일부임을 증명하였다.


두 번째 알고리즘으로 프림 알고리즘이 있다.


크루스칼 알고리즘이 최소신장트리를 이곳 저곳 따로 완성시켜나가는 반면, 프림은 한 지점부터 확장시켜나가면서 완성시킨다.


1) 아무 노드나 시작점으로 정한다.(A)

2) 그 노드에 연결된 간선 중 비용이 가장 작은 간선(A-B)을 포함시킨다. 단 B는 지금까지 연결되지 않은 노드이다.

3) A, B 아무 노드나 정하고 2를 반복한다.


최소신장트리를 완성해 나가는 과정은 선형시간에 가능하다.(크루스칼의 사이클을 확인하는 과정은 디스조인트-셋으로 간소화된다)

시간을 잡아먹는 작업은 가장 비용이 작은 간선을 찾는 작업에서 비롯된다.

크루스칼은 처음에 모든 간선을 정렬해야 하고, 프림은 각 단계의 노드와 연결된 간선들을 정렬해야한다.


따라서 시간 복잡도는 다음과 같다. 




3. 최소신장트리를 구해야 하는 문제들



1) 1197 최소 스패닝 트리 : https://www.acmicpc.net/problem/1197


말 그대로인 문제.


풀이 : http://js1jj2sk3.tistory.com/23


2) 1922 네트워크 연결 : https://www.acmicpc.net/problem/1922


모든 노드를 연결하는 최소비용을 구해야 한다. 최소신장트리의 정의를 알고 있는지 묻고 있다.


풀이 : http://js1jj2sk3.tistory.com/24


3) 1647 도시 분할 계획 : https://www.acmicpc.net/problem/1647


그래프를 두 최소신장트리로 나누려고 한다. 최소신장트리의 간선 수는 (노드의 수(n) - 1)임을 생각해보자.


풀이 : http://js1jj2sk3.tistory.com/25


4) 9372 상근이의 여행 : https://www.acmicpc.net/problem/9372


가장 적은 종류의 비행기를 탄다 = 가장 간선의 수가 적은 트리는 ? = 최소신장트리


풀이 : http://js1jj2sk3.tistory.com/26


5) 2887 행성 터널 : https://www.acmicpc.net/problem/2887


최소신장트리를 구해야 하는 문제임은 명확하지만, 간선이 모호하게 주어졌다. 


풀이 : http://js1jj2sk3.tistory.com/27


6) 1944 복제 로봇 : https://www.acmicpc.net/problem/1944


어느 지점을 노드화 시키고, 간선의 비용은 어떻게 구할 것인지가 중요하다.


풀이 : http://js1jj2sk3.tistory.com/28

3780. 네트워크 연결


문제

종빈이는 아주 큰 그룹의 총수다. 이 그룹은 1부터 N번까지의 번호로 구분할 수 있는 N개의 기업을 운영하고 있다. 현재 각 기업은 서로 독립적인 자체 컴퓨팅 및 통신센터를 가지고 있다.

어느 날 종빈이는 계열사의 CTO인 서현이에게 서비스 개선을 위해 각 기업의 서버를 네트워크로 연결하여 단일 통신센터에서 관리 가능한 클러스터 형태로 구성할 것을 제안했다. 종빈이의 제안을 들은 서현이는 다음과 같은 병합 과정을 고안해냈다.

  1. 클러스터 A를 제공하는 기존에 존재하는 센터 I를 고른다.
  2. 클러스터 B를 제공하는 기업 J를 고른다. B는 A가 아닌 임의의 클러스터이며, J는 센터가 아닐 수 있다.
  3. I와 J를 통신 라인으로 연결한다. 이때 기업 I와 J를 잇는 라인의 길이는 |I – J|(mod 1000)이다.
  4. 위 방식을 통해 클러스터 A와 B는 새로운 클러스터로 합쳐지며, 이 클러스터는 B의 센터에 의해 제공된다.

이러한 병합 과정을 거치던 중에, 각 기업에서 현재 센터까지 연결되는 라인의 길이가 총 얼마나 되는지에 관한 문의가 들어왔다. 서현이를 위해 병합하는 과정과 그 과정에서 통신센터와 각 기업을 잇는 라인의 길이를 구하는 프로그램을 작성해보자.

입력

입력은 여러 개의 테스트케이스로 주어진다. 입력의 첫 번째 줄에는 테스트 케이스의 개수 T가 주어진다. 각 테스트 케이스에는 기업의 수를 나타내는 N(5 ≤ N ≤ 20,000)이 주어진다. 다음은 몇 개의 줄에 걸쳐 아래 두 가지 종류의 명령어가 들어온다.

  • E I – 기업 I와 현재 I의 센터까지의 거리를 출력한다. 
  • I I J – 센터 I를 기업 J에 연결한다.

각 테스트케이스의 끝에는 단어 O가 주어진다. 각 테스트케이스에서 명령어의 총 개수는 200,000개를 넘지 않으며, 그중 I 명령어의 개수는 N개보다 작다.

출력

E 명령어가 들어올 때마다 한 줄에 해당 거리를 출력한다.

예제 입력 

1
4
E 3
I 3 1
E 3
I 1 2
E 3
I 2 4
E 3
O

예제 출력 

0
2
3
5


1. 접근


각 기업별로 서버를 갖고 있다. 서버는 네트워크로 연결될 수 있고, 네트워크에는 센터 서버가 있다.

따라서 현재 기업마다의 센터는 단일 네트워크이자, 센터 서버이다.


이제 네트워크들을 I 명령어로 연결하고자 한다.

I 1 2 라면, 센터 서버인 1서버와, 2서버를 연결한다. 주의할 점은 첫번째 서버는 반드시 센터 서버이고,

두번째 서버는 그 서버가 속한 네터워크의 센터 서버가 아닐 수도 있다.


또한 연결된 이후에는, 두 네트워크는 하나의 네트워크가 되며, 센터 서버는 두번 째 서버가 담당한다.


우리는 서버들을 노드로 표현할 수 있으며, 서버들의 종속관계를 간선으로 표현할 수 있다.

간선의 크기는 |i - j|(mod 1000)으로 지정된다.


이때 한 기업이 주어지면, 기업의 서버에서, 그 서버의 센터 서버까지의 경로의 길이 합을 구해야 한다.

그래프적인 방법으로 접근해보자.



2. 풀이


네트워크는 집합으로, 센터는 루트 노드로 생각한다면, 바로 디스조인트-셋을 사용할 생각이 든다.

센터노드는 하나이므로, 노드에서 나가는 간선은 최대 한개이기 때문이다.


하지만 문제는 길이 합을 구해야 하기 때문에, 매번 경로를 탐색한다면,

명령어의 수(200,000) * 최악의 경로(20,000)으로 시간초과가 날 것이다.


따라서 디스조인트의 연산을 활용하여, 연결 시 마다 네트워크의 종속관계를 갱신해주고, 길이도 갱신해보도록 하자.


연결을 담당하는 union()시 i는 기존의 센터이고, j는 새로운 센터가 될 서버이므로 매우 쉽게 연결과 길이 정의가 가능하다.


이제 find()가 길이 합을 계산해주는 역할을 해야 한다. find는 부모를 재귀적으로 따라가서 최종적으로 루트노드를 반환해주는 연산이다.

우리가 원하는 연산은 센터를 제외한 나머지 노드의 부모를 센터로 지정하고, 길이도 갱신해줘야 한다.

또한 x서버에서 센터까지 가는 경로는 유일하므로, find를 조금 바꿔주면 가능하다.


재귀적으로 루트노드 갱신을 해주는 원래 기능에, 길이 갱신만 추가하면 된다.

len[x] += len[parent[x]]


다음 예시로 어떤 동작을 해야하는지 알아보자.


I 1, 2

I 2, 3

E 1


위와 같은 경우에, union()을 두번 수행한 결과 1번 노드는 2번 노드를, 2번 노도는 3번 노드를 가리키고 있을 것이다.

이제 find(1)을 수행하면, 1번 노드는 3번 노드를 가리켜야 하고, 길이는 2번 노드의 것을 추가하여 갱신되어야 한다.



3. 코드


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
#include <stdio.h>
#include <algorithm>
#include <math.h>
using namespace std;
 
typedef pair<intint> p;
int t, n, a, b, c, i;
int parent[20001];
int len[20001];
 
int find(int x) {
    if (parent[x] == x)
        return x;
    else {
        int tmp = find(parent[x]);
        len[x] += len[parent[x]];
        parent[x] = tmp; 
 
        return tmp;
    }
}
 
void unite(int x, int y) {
    len[x] = abs(x - y) % 1000;
    parent[x] = y;
}
 
int main() {
    scanf("%d"&t);
    while (t--) {
        scanf("%d"&n);
        for (i = 0; i <= n; ++i) {
            parent[i] = i;
            len[i] = 0;
        }
        while (1) {
            scanf(" %c"&c);
            if (c == 'E') {
                scanf("%d"&a);
                find(a);
                printf("%d\n", len[a]);
            }
            else if (c == 'I') {
                scanf("%d %d"&a, &b);
                unite(a, b);
            }
            else
                break;
        }
    }
    return 0;
}
cs


재귀를 알맞게 구성해내는 경험이 더러 필요하다.

'알고리즘 > Disjoint-set 디스조인트-셋' 카테고리의 다른 글

백준) 10775 공항  (0) 2017.07.29
백준) 9938 방 청소  (0) 2017.07.29
백준) 4195 친구 네트워크  (0) 2017.07.28
백준) 1976 여행 가자  (0) 2017.07.28
백준) 1717 집합의 표현  (0) 2017.07.28

+ Recent posts