티스토리 뷰
https://www.acmicpc.net/problem/9466
9466번: 텀 프로젝트
이번 가을학기에 '문제 해결' 강의를 신청한 학생들은 텀 프로젝트를 수행해야 한다. 프로젝트 팀원 수에는 제한이 없다. 심지어 모든 학생들이 동일한 팀의 팀원인 경우와 같이 한 팀만 있을
www.acmicpc.net
이 문제를 처음 풀고나서 위상정렬 푼 것이 조금 마음에 들지 않아서 구글링을 했었다. 검색한 블로그들을 읽어보니 DFS를 이용한 좋은 풀이 방법이 존재했다.
대부분의 블로그들이 다 똑같은 풀이 방법만 적혀 있었기 때문에, 내가 푼 다른 풀이를 적어놓기로 했다. 개인적으로 문제를 풀 때 다양한 풀이 방법을 접해보는 것이 좋다고 생각하기 때문이다.
문제를 읽었을 때 다음과 같은 풀이들을 생각했다.
- 모든 정점(학생)에 대해 DFS
- 문제점 1 : 방문 체크를 하면서 DFS를 해야하는데 자기 자신을 처음에 방문체크하면 나중에 방문했던 정점을 만났을 때 처음 시작점에 대한 정보가 없어서 싸이클인지 알 수 없다.
ex. 1 - 2 - 3 - 1 이라면 1 부터 DFS를 해서 3까지 왔을 때, 이미 방문한 점인 1이 싸이클을 완성하는 정점인지, 이전에 다른 경우에서 방문했던 정점인지, 중간에 방문했던 정점인지 알 수가 없다. 해당 정보를 전달하면서 DFS를 진행하는 방법은 떠오르지 않았다.
- 문제점 2 : 다음과 같은 경우를 생각해보았다.
학생 1 -> 2 / 학생 2 -> 3 / 학생 3-> 4/ ..... / 학생 n-1 -> n / 학생 n -> n
위와 같은 경우 1에서 DFS를 한다면 총 n번, 2는 n-1번, 3은 n-2번... 총 n * n/2 번을 방문하게 된다. 정점의 개수가 n이므로 이 방법은 무조건 시간초과가 발생할 것이다.
- 유니온 - 파인드
학생의 번호를 부모, 학생이 선택한 다른 학생을 자식으로 두고 유니온 파인드를 통해 집합을 만들 수 있지 않을까? 라는 생각을 해봤지만 싸이클을 체크할 수 있는 방법은 전혀 떠올리지 못했다.
위 2가지 경우의 풀이를 포기하고 위상정렬로 풀이가 가능한가에 대해 생각해보았다. 위상정렬이 싸이클을 판별하는 대표적인 방법 중 하나이기 때문이다. 또한 해당 문제는 학생이 다른 학생을 가리키고 있으므로 명백히 DAG 라고 볼 수 있다.
하지만 이 풀이도 문제가 발생한다고 생각이 들었다. 학생이 자기 자신을 가리킬 때는 결과가 틀리게 나올 것 같았기 때문이다. 아래는 문제의 예제 입력 중 첫 번째 case 이다.
위 그래프를 위상정렬할 때 deg 와 queue를 정리해보면
deg[1] = 1 , deg[2] = 0, deg[3] = 2, deg[4] = 1, deg[5] = 0, deg[6] = 1, deg[7] = 1 이 되고 q에는 {2,5} 가 들어간다.
deg[3] = 2의 경우 자기 자신을 가리키는 edge는 포함하지 않은 경우이다.
위 내용대로 위상정렬을 할 경우 1과 5를 방문하면서 deg[3] 은 0 이 되고 싸이클을 이루지 않는다는 결과가 나올 것이다. 따라서 틀린 답이 나오게 된다.
또한 deg[3] 에 자기 자신을 가리키는 edge도 포함해 증가시키고 graph[3] 에 넣더라도 3이 3을 방문하면서 deg[3] = 0 이 되지않을까 하는 생각이 들었다.
하지만 3이 3을 방문하면서 0 이 되는 경우는 발생할 수 없다. 1과 5에 방문해서 deg[3] 이 3에서 1이 되었다고 하더라도, 0이 되지않으므로 deg[3] 은 q에 들어가지 않는다. 3이 q에 들어가려면 3->3 edge가 위상정렬이 진행되면서 지워져야 하는데 이는 절대로 발생할 수 없다. 3을 방문함으로써 3->3 edge가 지워지는데 3->3edge 때문에 deg[3] 이 0 이 되지 않기 때문이다.
따라서 그냥 평범한 위상정렬 처럼 진행해도 상관이 없다. 매 Test case 마다 위상정렬을 진행한 후에 deg값이 0인 정점을 세어주고 출력하면 된다. 마지막에 graph 벡터와 deg 배열을 초기화 해주기만 하면 된다.
Code
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <deque>
#include <set>
#include <stack>
#include <map>
#include <string>
#include <string.h>
#include <cstring>
#include <cmath>
#define fastio ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
#define ll long long
#define pii pair<int,int>
#define pll pair<ll,ll>
#define INTMAX 2'100'000'000
#define MOD 1'000'000'007
using namespace std;
int deg[100001];
vector <int> v[100001];
int t,n;
void topology() {
queue <int> q;
for (int i = 1; i <= n; i++) {
if (!deg[i])
q.push(i);
}
while (!q.empty()) {
int cur = q.front();
q.pop();
for (int i : v[cur]) {
deg[i]--;
if (!deg[i])
q.push(i);
}
}
}
int main() {
fastio;
cin >> t;
while (t--) {
int cnt = 0;
memset(deg, 0, sizeof(deg));
cin >> n;
int x;
for (int i = 1; i <= n; i++) {
cin >> x;
deg[x]++;
v[i].push_back(x);
}
topology();
for (int i = 1; i <= n; i++) {
if (!deg[i])
cnt++;
}
for (int i = 1; i <= n; i++) {
if (!deg[i])
cnt++;
}
cout << cnt << endl;
for (int i = 1; i <= n; ++i) {
v[i].clear();
}
}
}
vector graph[100001] 은 보통 메모리 초과가 나지만 위 문제의 경우 edge가 무조건 n개만 존재하기 때문에 메모리 초과가 발생하지 않는다. 같은 이유로 q로 인한 메모리 초과나 graph 배열을 clear 할 때 시간초과 같은 것은 발생하지 않는다.
위상 정렬은 싸이클이 없는 정점만 방문하므로 시간은 O(n) 이다. n <= 100,000 이므로 시간초과는 발생하지 않는다.
'PS' 카테고리의 다른 글
[백준 2437] 저울 (1) | 2023.09.02 |
---|---|
[C++] 백준1025. 제곱수 찾기 (0) | 2023.03.19 |
N-Queen (0) | 2023.02.18 |
- Total
- Today
- Yesterday
- CUDA VISUAL STUDIO 2022 지원
- 디자인 패턴
- Ambiguity
- 셀룰러네트워크
- 혼잡제어
- 비동기전송모드
- 컴파일러
- Extension to Regular Expression
- 클래스 모델링
- Regular Expression
- Proper CFL
- ngp 실행
- 인터네트워크
- NGP-ERROR
- 소프트웨어 공학
- 아키텍처 설계
- 인터넷프로토콜
- 설계 원리
- lan
- 백준 2437
- 소프트웨어공학
- 전송계층프로토콜
- ngp 오류
- Instant-NGP
- ATM
- Instnat-ngp
- Transition Function
- 회선교환
- Compiler
- 컴퓨터네트워크
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |