문제

https://www.acmicpc.net/problem/9466


풀이


풀이 1

  • 78% 시간 초과
import sys
input = sys.stdin.readline
sys.setrecursionlimit(10 ** 6)

T = int(input().rstrip())

def dfs(cur, team) :

    for g in graph[cur] :
        if not isTeam[g] :
            if g in team :
                idx = team.index(g)
                for i in range(idx, len(team)): isTeam[team[i]] = 1
                return
            else :
                team.append(g)
                dfs(g, team)

for tc in range(T) :
    n = int(input().rstrip())

    students = list(map(int, input().rstrip().split()))
    students.insert(0, 0) # 1번 idx에 1번 학생이 오도록

    graph = [[] for _ in range(n + 1)]

    for i in range(1, n + 1) : graph[i].append(students[i])
    isTeam = [0 for _ in range(n + 1)]

    for i in range(1, n + 1) :
        if not isTeam[i] :
            team = [i]
            dfs(i, team)

    print(n - sum(isTeam))


풀이 2

  • 80% 시간 초과
import sys
input = sys.stdin.readline
sys.setrecursionlimit(10 ** 8)

T = int(input().rstrip())

def dfs(cur, team, depth) :

    for g in graph[cur] :
        if not isTeam[g] :
            if str(g) in team :
                idx = team[str(g)]
                for i in team :
                    if team[str(i)] >= idx : isTeam[int(i)] = 1
                return
            else :
                team[str(g)] = depth
                dfs(g, team, depth + 1)

for tc in range(T) :
    n = int(input().rstrip())

    students = list(map(int, input().rstrip().split()))
    students.insert(0, 0) # 1번 idx에 1번 학생이 오도록

    graph = [[] for _ in range(n + 1)]

    for i in range(1, n + 1) : graph[i].append(students[i])
    isTeam = [0 for _ in range(n + 1)]

    for i in range(1, n + 1) :
        if not isTeam[i] :
            team = {str(i) : 0}
            dfs(i, team, 1)

    print(n - sum(isTeam))


풀이2 설명

문제를 읽고 처음 떠올린 풀이 방법은

  • 그래프로 입력 받고 dfs로 cycle이 있는지 확인
    • cycle : 이미 visited == 1 인데 또 방문하는 경우
  • input이 100,000이므로 O(n^2)가 되지 않도록 주의

따라서

    1. 팀을 이루지 못한 학생에 대해서 (isTeam == 0) dfs를 돌리는데

    2. 다음 학생이 cycle을 이루면(g in Team), 사이클인 부분부터 끝까지 team 만든 것으로 처리 (isTeam = 1)

    3. cycle 이루지 못하면, 일단 team에 추가하고 다음 학생 탐색 진행

    4. 전체 학생에서 팀을 이룬 학생(isTeam == 1) 수를 뺀 것이 정답

과 같이 코드를 작성했다.

78% 에서 시간 초과되었고 혹시 인덱스를 직접 찾는 부분에서 시간이 많이 소요되나 싶어서 dictionary로도 해보았지만, 역시 근본적 해결은 아니었다..

dictionary를 이용하였을 때에는 key에 학생의 번호, value에 몇 번 째 깊이에서 team에 추가되었는지를 담아서

cycle이 있을 때 value로 cycle에 해당하는 학생들을 확인했다.


최종 풀이

  • 60908 KB, 2656 ms
import sys
input = sys.stdin.readline
sys.setrecursionlimit(10 ** 8)

def dfs(cur) :
    global ans

    visited[cur] = 1
    team.append(cur)
    nxt = students[cur]

    if visited[nxt] :
        if nxt in team :
            ans += team[team.index(nxt) : ]
        return
    else :
        dfs(nxt)

T = int(input().rstrip())

for _ in range(T) :
    n = int(input())
    students = list(map(int, input().rstrip().split()))
    students.insert(0, 0)
    visited = [0 for _ in range(n + 1)]
    ans = []

    for i in range(1, n + 1) :
        if not visited[i] :
            team = []
            dfs(i)

    print(n - len(ans))


최종 풀이 설명

앞선 풀이에서 탐색하였더라도 팀을 이루지 못한 경우에 다시 탐색하여서 중복이 발생하고 있었다.

하지만, 한 번 팀을 이룰 수 없는 것을 확인하면 다시 확인할 필요가 없기 때문에 이 부분이 중복되지 않도록 코드를 다시 작성하였다.

1. 방문하지 않은 경우에 대해서 dfs를 하는데, 

2. dfs 내용은

  • 일단 방문 처리
  • team으로 추가
  • 다음 노드 확인
    • 다음 노드를 방문 한 적 있고 team에 있으면(= cycle), cycle인 부분만 ans에 추가
    • 다음 노드 방문했는데 team에 없으면 의미 없으므로 return
    • 다음 노드 방문 한 적 없으면 계속 dfs 탐색 진행

3. ans에 team을 이룬 학생이 담기므로, 전체 학생에서 ans의 길이를 빼주면 팀을 이루지 못한 학생 수를 구할 수 있다.


배운 점

중복 탐색을 하고 있지는 않은지 잘 확인하기 !


Leave a comment