March 07, 2021
https://www.acmicpc.net/problem/9466
문제
이번 가을학기에 ‘문제 해결’ 강의를 신청한 학생들은 텀 프로젝트를 수행해야 한다. 프로젝트 팀원 수에는 제한이 없다. 심지어 모든 학생들이 동일한 팀의 팀원인 경우와 같이 한 팀만 있을 수도 있다. 프로젝트 팀을 구성하기 위해, 모든 학생들은 프로젝트를 함께하고 싶은 학생을 선택해야 한다. (단, 단 한 명만 선택할 수 있다.) 혼자 하고 싶어하는 학생은 자기 자신을 선택하는 것도 가능하다.
학생들이(s1, s2, …, sr)이라 할 때, r=1이고 s1이 s1을 선택하는 경우나, s1이 s2를 선택하고, s2가 s3를 선택하고,…, sr-1이 sr을 선택하고, sr이 s1을 선택하는 경우에만 한 팀이 될 수 있다.
예를 들어, 한 반에 7명의 학생이 있다고 하자. 학생들을 1번부터 7번으로 표현할 때, 선택의 결과는 다음과 같다.
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|
| 3 | 1 | 3 | 7 | 3 | 4 | 6 |
위의 결과를 통해 (3)과 (4, 7, 6)이 팀을 이룰 수 있다. 1, 2, 5는 어느 팀에도 속하지 않는다.
주어진 선택의 결과를 보고 어느 프로젝트 팀에도 속하지 않는 학생들의 수를 계산하는 프로그램을 작성하라.
입력
첫째 줄에 테스트 케이스의 개수 T가 주어진다. 각 테스트 케이스의 첫 줄에는 학생의 수가 정수 n (2 ≤ n ≤ 100,000)으로 주어진다. 각 테스트 케이스의 둘째 줄에는 선택된 학생들의 번호가 주어진다. (모든 학생들은 1부터 n까지 번호가 부여된다.)
출력
각 테스트 케이스마다 한 줄에 출력하고, 각 줄에는 프로젝트 팀에 속하지 못한 학생들의 수를 나타내면 된다.
그래프의 사이클을 확인하는 문제였다. 처음에는 아무 생각없이 유니온 파인드로 풀어야겠다고 생각했다가 유향 그래프에서는 유니온 파인드를 사용할 수 없다는 것을 깨닫고 다시 코드를 작성했다.
이 문제에서 가장 어려웠던 부분이 사이클을 확인하는 작업을 빠른 시간 안에 마치는 것이다. N^2 도 시간 초과가 난다. 나는 사이클을 찾기 위해 몇가지 시도를 해보았다.
사이클이 생기면 역으로 돌아오면서 거쳐온 정점을 세기.
사이클을 다시 한번 돌아보면서 사이클에 있는 정점 세기.
몇번 이동했는지를 기록하면서 체크하는 방법
좀 더 여러가지 예를 들어보자.
1 - 1 + 1 = 1 이 된다.3 - 1 + 1 = 3 로 총 세 개의 정점이 사이클에 포함되어 있음을 확인할 수 있다.사실 3번 방법을 사용하면 O(N)만에 헤결 할 수 있을 줄 알았는데 시간초과를 받았다. 그래서 문제점을 찾아보던 중, 어차피 정점은 항상 한쌍을 만드는데 adjacency list를 만들고 for 문을 넣어둔 것을 발견했다. 바로 변수로 할당하는거나 for문을 한번 도는거나 시간이 비슷할 줄 알았는데, 반복문 탈출을 위해서는 반복문을 무조건 2번 돌아야하니 바로 변수로 선언하는 것 보다 두배 더 오래걸린다. 아주 간단한 부분이었는데, 놓친게 아쉽다.
' '
#include <cstdio>
#include <vector>
using namespace std;
const int MAX = 100001;
bool visited[MAX];
bool searchDone[MAX];
int stepCount[MAX];
int cnt = 0;
void dfs(int now, int origin, int adj[MAX][1]){
visited[now] = true;
int next = adj[now][0];
if (!visited[next]){
stepCount[next] = stepCount[now] + 1;
dfs(next, origin, adj);
}
else if (!searchDone[next]) {
cnt += (stepCount[now] - stepCount[next] + 1);
}
searchDone[now] = true;
}
int main (){
int T;
scanf("%d", &T);
while(T--){
int students;
scanf("%d", &students);
int selections[MAX][1];
for (int i = 0 ; i <= students ; i++){
visited[i] = false;
searchDone[i] = false;
stepCount[i] = 0;
}
for (int i = 1 ; i <= students ; i++){
scanf("%d", &selections[i][0]);
}
for (int i = 1 ; i <=students ; i++){
if (!visited[i]){
dfs(i, i, selections);
}
}
printf("%d\n", students - cnt);
cnt = 0;
}
}