[백준] 1043 거짓말
[Gold IV] 거짓말 - 1043
성능 요약
메모리: 32544 KB, 시간: 40 ms
분류
그래프 이론, 자료 구조, 그래프 탐색, 분리 집합
문제 설명
지민이는 파티에 가서 이야기 하는 것을 좋아한다. 파티에 갈 때마다, 지민이는 지민이가 가장 좋아하는 이야기를 한다. 지민이는 그 이야기를 말할 때, 있는 그대로 진실로 말하거나 엄청나게 과장해서 말한다. 당연히 과장해서 이야기하는 것이 훨씬 더 재미있기 때문에, 되도록이면 과장해서 이야기하려고 한다. 하지만, 지민이는 거짓말쟁이로 알려지기는 싫어한다. 문제는 몇몇 사람들은 그 이야기의 진실을 안다는 것이다. 따라서 이런 사람들이 파티에 왔을 때는, 지민이는 진실을 이야기할 수 밖에 없다. 당연히, 어떤 사람이 어떤 파티에서는 진실을 듣고, 또다른 파티에서는 과장된 이야기를 들었을 때도 지민이는 거짓말쟁이로 알려지게 된다. 지민이는 이런 일을 모두 피해야 한다.
사람의 수 N이 주어진다. 그리고 그 이야기의 진실을 아는 사람이 주어진다. 그리고 각 파티에 오는 사람들의 번호가 주어진다. 지민이는 모든 파티에 참가해야 한다. 이때, 지민이가 거짓말쟁이로 알려지지 않으면서, 과장된 이야기를 할 수 있는 파티 개수의 최댓값을 구하는 프로그램을 작성하시오.
입력
첫째 줄에 사람의 수 N과 파티의 수 M이 주어진다.
둘째 줄에는 이야기의 진실을 아는 사람의 수와 번호가 주어진다. 진실을 아는 사람의 수가 먼저 주어지고 그 개수만큼 사람들의 번호가 주어진다. 사람들의 번호는 1부터 N까지의 수로 주어진다.
셋째 줄부터 M개의 줄에는 각 파티마다 오는 사람의 수와 번호가 같은 방식으로 주어진다.
N, M은 50 이하의 자연수이고, 진실을 아는 사람의 수는 0 이상 50 이하의 정수, 각 파티마다 오는 사람의 수는 1 이상 50 이하의 정수이다.
출력
첫째 줄에 문제의 정답을 출력한다.
문제 풀이
문제 분석
이 문제는 파티에 참석하는 사람들이 주어졌을 때, 진실을 아는 사람이 있는 파티에서는 진실을 말해야 하고, 그렇지 않은 파티에서는 과장된 이야기를 할 수 있다고 할 때, 과장된 이야기를 할 수 있는 파티의 최대 개수를 구하는 문제입니다. 즉, 진실을 아는 사람이 참석하는 파티와 연결된 모든 파티에서는 진실을 말해야 합니다.
접근 방법
이 문제는 분리 집합(Disjoint Set) 자료구조를 사용하여 해결할 수 있습니다.
- 각 사람을 노드로 보고, 같은 파티에 참석한 사람들을 같은 집합으로 묶습니다.
- 진실을 아는 사람이 속한 집합을 찾습니다.
- 각 파티를 순회하면서, 해당 파티에 참석한 사람이 진실을 아는 사람이 속한 집합에 속하는지 확인합니다. 만약 속한다면, 그 파티에서는 과장된 이야기를 할 수 없습니다.
- 과장된 이야기를 할 수 있는 파티의 개수를 세어 출력합니다.
구현 설명
1. 입력 및 초기화
- 사람 수
n과 파티 수m을 입력받습니다. - 진실을 아는 사람들의 번호를 입력받아
knows집합에 저장합니다. parents리스트를 초기화합니다. 이 리스트는 각 사람의 부모 노드를 저장하며, 초기에는 각 사람이 자기 자신을 부모로 가리키도록 설정합니다. 즉, 각 사람은 독립적인 집합에 속하게 됩니다.parties리스트를 초기화합니다. 이 리스트는 각 파티에 참석하는 사람들의 번호 목록을 저장합니다.
2. Union 연산 수행
- 각 파티에 대해, 해당 파티에 참석한 사람들을 같은 집합으로 묶습니다. 이는
union함수를 사용하여 수행됩니다.union(a, b)함수는a와b가 속한 집합을 합치는 역할을 합니다. 먼저find(a)와find(b)를 호출하여 각각의 루트 노드를 찾습니다. 만약 두 루트 노드가 다르다면,b의 부모를a로 설정하여 두 집합을 합칩니다.- 이 과정을 모든 파티에 대해 수행함으로써, 같은 파티에 참석한 사람들은 모두 같은 집합에 속하게 됩니다.
3. 진실을 아는 사람의 집합 갱신
knows에 있는 사람들의 대표 노드를 찾아서knows를 갱신합니다. 이제knows에는 진실을 아는 사람들의 집합의 대표 노드들이 저장됩니다.
4. 파티 순회 및 결과 계산
- 각 파티를 순회하면서, 해당 파티에 참석한 사람 중 한 명이라도 진실을 아는 사람이 속한 집합에 속하는지 확인합니다.
- 만약 파티에 참석한 사람 중 한 명이라도
find(x) in knows를 만족한다면, 해당 파티에서는 과장된 이야기를 할 수 없습니다. - 만약 파티에 참석한 모든 사람이 진실을 아는 사람이 속한 집합에 속하지 않는다면, 해당 파티에서는 과장된 이야기를 할 수 있으며,
answer를 1 증가시킵니다.
- 만약 파티에 참석한 사람 중 한 명이라도
⏱복잡도 분석
- 시간 복잡도:
find함수의 시간 복잡도는 경로 압축을 사용했으므로 거의 O(1)에 가깝습니다 (정확히는 amortized O(α(n)) 여기서 α(n)은 매우 느리게 증가하는 Ackermann 함수의 역함수).union함수의 시간 복잡도 또한 거의 O(1)입니다.- 파티의 수 m, 각 파티의 평균 참석자 수를 k라고 할 때,
union연산을 수행하는 데 O(mk) 시간이 걸립니다. - 각 파티를 순회하면서 진실을 아는 사람이 있는지 확인하는 데 O(mk) 시간이 걸립니다.
- 따라서 전체 시간 복잡도는 O(mk)입니다. 문제에서 n과 m은 50이하의 수이기 때문에 시간 제한에 걸리지 않습니다.
- 공간 복잡도:
parents리스트는 O(n)의 공간을 사용합니다.knows집합은 최대 O(n)의 공간을 사용합니다.parties리스트는 최대 O(m * k)의 공간을 사용합니다 (k는 파티당 평균 참석자 수).- 따라서 전체 공간 복잡도는 O(n + mk)입니다.
핵심 포인트
- 분리 집합 활용: 같은 파티에 참석한 사람들을 같은 집합으로 묶어 진실을 아는 사람과 관련된 모든 사람들을 효과적으로 관리합니다.
- Union-Find 최적화: 경로 압축을 사용하여
find연산의 시간 복잡도를 상수 시간에 가깝게 만들어 전체 실행 시간을 단축합니다. - 진실을 아는 사람 기준: 파티에 참석한 사람들 중 한 명이라도 진실을 아는 사람과 같은 집합에 속하면, 해당 파티에서는 과장된 이야기를 할 수 없다는 점을 활용합니다.
풀이 코드
import sys
input = sys.stdin.readline
n,m = map(int, input().split())
x = list(map(int, input().split()))
knows = set(x[1:])
parents = [i for i in range(n + 1)]
def find(x):
if parents[x] != x:
parents[x] = find(parents[x])
return parents[x]
def union(a,b):
a, b = find(a), find(b)
if a != b:
parents[b] = a
parties = []
for i in range(m):
x = list(map(int, input().split()))
members = x[1:]
parties.append(members)
for i in range(1, len(members)):
union(members[0], members[i])
knows = set(find(t) for t in knows)
answer = 0
for party in parties:
for x in party:
if find(x) in knows:
break
else:
answer += 1
print(answer)