문제 설명
https://www.acmicpc.net/problem/20040
[문제]
사이클 게임은 두 명의 플레이어가 차례대로 돌아가며 진행하는 게임으로, 선 플레이어가 홀수 번째 차례를, 후 플레이어가 짝수 번째 차례를 진행한다. 게임 시작 시 0 부터 n − 1 까지 고유한 번호가 부여된 평면 상의 점 n 개가 주어지며, 이 중 어느 세 점도 일직선 위에 놓이지 않는다. 매 차례 마다 플레이어는 두 점을 선택해서 이를 연결하는 선분을 긋는데, 이전에 그린 선분을 다시 그을 수는 없지만 이미 그린 다른 선분과 교차하는 것은 가능하다. 게임을 진행하다가 처음으로 사이클을 완성하는 순간 게임이 종료된다. 사이클 C는 플레이어가 그린 선분들의 부분 집합으로, 다음 조건을 만족한다.
C에 속한 임의의 선분의 한 끝점에서 출발하여 모든 선분을 한 번씩만 지나서 출발점으로 되돌아올 수 있다.
문제는 선분을 여러 개 그리다 보면 사이클이 완성 되었는지의 여부를 판단하기 어려워 이미 사이클이 완성되었음에도 불구하고 게임을 계속 진행하게 될 수 있다는 것이다. 이 문제를 해결하기 위해서 게임의 진행 상황이 주어지면 몇 번째 차례에서 사이클이 완성되었는지, 혹은 아직 게임이 진행 중 인지를 판단하는 프로그램을 작성하려 한다.
입력으로 점의 개수 n과 m 번째 차례까지의 게임 진행 상황이 주어지면 사이클이 완성 되었는 지를 판단하고, 완성되었다면 몇 번째 차례에서 처음으로 사이클이 완성된 것인지를 출력하는 프로그램을 작성하시오.
[입력]
입력은 표준 입력을 사용한다. 입력의 첫 번째 줄에는 점의 개수를 나타내는 정수 3 ≤ n ≤ 500,000 과 진행된 차례의 수를 나타내는 정수 3 ≤ m ≤ 1,000,000 이 주어진다. 게임에서 사용하는 n개의 점에는 0 부터 n − 1 까지 고유한 번호가 부여되어 있으며, 이 중 어느 세 점도 일직선 위에 놓이지 않는다. 이어지는 m 개의 입력 줄에는 각각 i번째 차례에 해당 플레이어가 선택한 두 점의 번호가 주어진다 (1 ≤ i ≤ m).
[출력]
출력은 표준 출력을 사용한다. 입력으로 주어진 케이스에 대해, m 번째 차례까지 게임을 진행한 상황에서 이미 게임이 종료되었다면 사이클이 처음으로 만들어진 차례의 번호를 양의 정수로 출력하고, m 번의 차례를 모두 처리한 이후에도 종료되지 않았다면 0을 출력한다.
[예제 입력 1]
6 5
0 1
1 2
2 3
5 4
0 4
[예제 출력 1]
0
[예제 입력 2]
6 5
0 1
1 2
1 3
0 3
4 5
[예제 출력 2]
4
풀이
문제에서 요구하는 사이클 검사를 union-find 알고리즘으로 적용할 수 있다는 것을 쉽게 파악할 수 있다. 문제 상 게임으로는 선분을 계속해서 그리며 사이클을 찾는다고 하지만, 점 대 점으로 보았을 때는 점들이 어떠한 그룹에 속하는 과정이기 때문에 union-find인 것이다.
이를 바탕으로 점들의 부모를 저장할 수 있는 par[] 배열을 만들고, 어떠한 점에 대해 그 그룹의 최상단 점을 반환할 수 있는 함수를 정의한다. 이후에 입력으로 들어오는 2개의 점에 대해 각각의 그룹을 대표하는 점을 찾고, 이들이 다르면 두 개의 그룹을 합하는 과정을(한 점의 부모를 다른 점으로 선언), 둘이 같다면 사이클이 생성된 것으로 파악하고 해당 라운드의 개수를 출력한다.
여담으로 자바의 경우 입력을 모두 받고 답안을 출력하는 경우 시간 초과가 발생해, 입력마다 알고리즘을 수행하며 사이클이 생성되면 바로 답을 출력하고 return을 통해 함수를 종료해버려야 시간 내에 통과가 가능했다.
public class Main {
static int n, m, par[];
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
n = Integer.parseInt(st.nextToken());
m = Integer.parseInt(st.nextToken());
par = new int[n];//부모 배열 초기화
Arrays.fill(par, -1);
for (int i = 1; i <= m; i++) {
int point1, point2;
st = new StringTokenizer(br.readLine());
point1 = Integer.parseInt(st.nextToken());
point2 = Integer.parseInt(st.nextToken());
point1 = findPar(point1);
point2 = findPar(point2);
if (point1 == point2) {//같으면 사이클 -> 출력
System.out.println(i);
return;
}
else {//같지 않으면 그룹 merge
par[point1]= point2;
}
}
System.out.println(0);
}
static int findPar(int point){//재귀 함수 기반 최상단 부모 반환
if (par[point] == -1)
return point;
return par[point] = findPar(par[point]);
}
}
결과
'Algorithm > 코드 풀이' 카테고리의 다른 글
9342번: 염색체 [JAVA] (0) | 2021.11.02 |
---|---|
1013번: Contact [JAVA] (0) | 2021.10.26 |
12757번: 전설의 JBNU [JAVA] (0) | 2021.10.03 |
1501번: 영어 읽기 [JAVA] (0) | 2021.10.03 |
4195번: 친구 네트워크 [JAVA] (0) | 2021.09.26 |