문제
https://www.acmicpc.net/problem/9466
풀이
import java.io.*;
import java.util.*;
public class Main{
static int T;
static int check;
static Set<Integer> team; // 팀원들의 집합
static Stack<Integer> stack; // 팀원을 추가하기 위한 스택
static List<Integer> list; // 임시 저장을 위한 리스트
static int[] arr; // 학생들이 누구랑 팀원이 되고 싶은지
static boolean[] visited; // 이미 학생을 체크했는지
static boolean flag; // 팀 구성이 완료되었는지
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st;
T = Integer.parseInt(br.readLine()); // 테스트 케이스 개수
StringBuilder sb = new StringBuilder();
for(int i = 0; i < T; i++){
int n = Integer.parseInt(br.readLine()); // 새로운 학생 수 정수 n 입력 받기
arr = new int[n]; // 배열 선언
visited = new boolean[n]; // 방문 배열 초기화
team = new HashSet<>(); // 팀 초기화
st = new StringTokenizer(br.readLine());
for(int j = 0; j < n; j++){
arr[j] = Integer.parseInt(st.nextToken()) - 1; // 학생 배열 초기화
}
for(int j = 0; j < n; j++){
stack = new Stack<>(); // 반복시마다 스택 초기화
list = new ArrayList<>(); // 반복 시마다 리스트 초기화
check = -1; // 기본 체크값 -1
dfs(j); // dfs 시작
if(!flag){ // 팀 구성이 완료되지 않았다면
for(int x : list){
System.out.println(x);
visited[x] = true; // 방문 배열 초기화
/*
이렇게 해주는 이유는 무엇인가?
5 5 4 5 3이라는 예시를 생각해보자.
1번에서 5번에 대한 방문을 사이클도 못 만들었으면서 true로 바꿔버리면
4 -> 5 -> 3이 되지 않기 때문이다.
즉, 사이클을 못 만들었다면 그 값은 다시 방문할 수 있음을 확인해준다.
*/
}
flag = false; // 플래그 초기화
}
}
int answer = n - team.size(); // 학생 수 - 팀 사이즈
sb.append(answer + "\n");
}
System.out.println(sb.toString());
}
private static void dfs(int start){
if(visited[start]){ // 이미 방문한 곳 체크
check = start; // 체크값을 시작 포인트로 정해줌
/*
예시를 들어 생각해보자.
2 -> 3
3 -> 4
4 -> 2이라고 생각하면,
처음 시작 2 -> 시작 3- > 시작4 -> 시작 2가 된다
이 때 시작 2는 이미 true(하단 코드 참고)가 되므로 이쪽의 분기문으로 들어온다
그럼 check = 2가 저장된다는 것이다.
이 체크값을 사이클 확인에 사용하므로 상단 코드에서 절대 겹칠 일이 없는 -1을 기본값으로 설정해두는 것.
*/
return;
}
visited[start] = true; // 방문 체크
stack.push(start); // 스택에 집어넣음(후입선출 자료구조를 위함)
dfs(arr[start]); // dfs 재귀 호출
int temp = stack.pop(); // 스택의 값 뽑음
list.add(temp); // 리스트에 넣음
if(temp == check){ // 만약 스택에서 나온 값이 check와 같다면
// 여기서 check 는 위의 예시라면 2고
// stack엔 2 3 4가 들어있을 것이다. 마지막에 분기문 들어갈 때는 stack에 안 들어가므로
team.addAll(list); // 2 3 4가 들어간 리스트를 set에 넣는다.
flag = true; // 이 dfs에서는 사이클이 존재했음을 체크해준다.
}
}
}
사이클을 체크하려고 하는 것이다. 사이클이 완성된다면 스택에 있던 값들을 사이클의 시작지점까지만 리스트에 더한 뒤에 집합에 넣어준다. 그리고 총 학생 수에서 이를 빼주어 답을 구한다.
풀이는 코드에 작성했기 때문에 생략한다.
아이디어만 잘 잡을 수 있다면 코드로 구현하는 것 자체는 어렵지 않았다. 다만, 다른 분들의 코드를 확인해보니 너무 자료구조를 난잡하게 쓴 부분들이 있는 것 같다 더욱 꼼꼼하고 간결하게 코드로 구현하는 방법을 연습해야될 것 같다고 느꼈다.
'문제 풀이 > 백준' 카테고리의 다른 글
[백준] 18870번. 좌표 압축(JAVA) (0) | 2024.11.14 |
---|---|
[백준] 1202번. 보석 도둑(JAVA) (0) | 2024.10.17 |
[백준] 10942번. 팰린드롬?(JAVA) (0) | 2024.10.16 |
[백준] 2473번. 세 용액(JAVA) (4) | 2024.10.13 |
[백준] 2166번. 다각형의 면적(JAVA) (1) | 2024.10.11 |