본문 바로가기

코딩테스트

[Python] DFS 문제 풀이 정리 2

문제 풀이 이어서 하기.

이번에는 골드 문제도 섞어서 도전!

 


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

 

10026번: 적록색약

적록색약은 빨간색과 초록색의 차이를 거의 느끼지 못한다. 따라서, 적록색약인 사람이 보는 그림은 아닌 사람이 보는 그림과는 좀 다를 수 있다. 크기가 N×N인 그리드의 각 칸에 R(빨강), G(초록)

www.acmicpc.net

import sys
sys.setrecursionlimit(10000000) 
input=sys.stdin.readline

n = int(input())
arr = []
visited = [[False]*n for _ in range(n)]
visited2 = [[False]*n for _ in range(n)]

for _ in range(n):
    arr.append(list(input().rstrip()))

dx = [0,0,-1,1]
dy = [-1,1,0,0]
# print(arr)
def DFS1(x, y): # 적록 색약 X
    visited[y][x] = True
    for i in range(4):
        nx = x+dx[i]
        ny = y+dy[i]
        if (0<=nx<n) and (0<=ny<n) and not visited[ny][nx] and arr[y][x]==arr[ny][nx]:
            # print(nx, ny, x, y)
            DFS1(nx, ny)

def DFS2(x, y): # 적록 색약 O
    visited2[y][x] = True
    for i in range(4):
        nx = x+dx[i]
        ny = y+dy[i]
        if arr[y][x]=='B': # 적록 색약인 경우 R==G이다.
            if 0<=nx<n and 0<=ny<n and not visited2[ny][nx] and arr[y][x]==arr[ny][nx]:
                DFS2(nx, ny)
        else:
            if 0<=nx<n and 0<=ny<n and not visited2[ny][nx] and arr[ny][nx]!='B':
                DFS2(nx, ny)
                
num1 = 0 # 적록 색약 X
num2 = 0 # 적록 색약 O
for i in range(n):
    for j in range(n):
        if not visited[i][j]:
            num1 += 1
            DFS1(j, i)
        if not visited2[i][j]:
            num2 += 1
            DFS2(j, i)

print(num1, num2)
  • 적록 색약인 경우 DFS를 따로 만든다. 'R'과 'G'를 같은 것으로 판단하게 한다. 즉, 'B'가 아니면 이동하게 하기!

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

 

1987번: 알파벳

세로 $R$칸, 가로 $C$칸으로 된 표 모양의 보드가 있다. 보드의 각 칸에는 대문자 알파벳이 하나씩 적혀 있고, 좌측 상단 칸 ($1$행 $1$열) 에는 말이 놓여 있다. 말은 상하좌우로 인접한 네 칸 중의

www.acmicpc.net

# 내 풀이(틀림)
import sys
sys.setrecursionlimit(10000000) 
input=sys.stdin.readline

r, c = map(int, input().split()) # 세로, 가로(행, 열)
visited = [[False]*c for _ in range(r)] # 방문한 위치 판별
arr= [] # 알파벳 입력받기
visit_al = [] # 방문한 알파벳 판별

for _ in range(r):
    arr.append(list(input().rstrip()))
# print(arr)

dx = [0,0,-1,1]
dy = [-1,1,0,0]

def DFS(x, y, k): 
    visited[y][x] = True
    for i in range(4):
        nx = x+dx[i]
        ny = y+dy[i]
        print(nx, ny, visit_al, k)
        if (0<=nx<c) and (0<=ny<r) and not visited[ny][nx] and arr[ny][nx] not in visit_al:
            # print(nx, ny, visit_al, k)
            visit_al.append(arr[ny][nx])
            k = DFS(nx, ny, k+1)

    return k

max_num = 0
for i in range(r):
    for j in range(c):
        if not visited[i][j]:
            visit_al = [arr[i][j]]
            max_num = max(max_num, DFS(j, i, 1))

print(max_num)
  • 백트래킹 과정이 없어서 틀렸다.
  • DFS돌았을 때 답이 아니면 되돌아가서 다른 길로 가야 한다! 예제 3번을 풀 수 없다...ㅜ
import sys
# sys.setrecursionlimit(10000000) 
input=sys.stdin.readline

r, c = map(int, input().split()) # 세로, 가로(행, 열)
arr= [] # 알파벳 입력받기
visit_al = set() # 방문한 알파벳 판별

for _ in range(r):
    arr.append(list(input().rstrip()))
# print(arr)

dx = [0,0,-1,1]
dy = [-1,1,0,0]
max_num = 0
def DFS(x, y, k): 
    global max_num
    max_num = max(max_num, k)
    for i in range(4):
        nx = x+dx[i]
        ny = y+dy[i]
        if (0<=nx<c) and (0<=ny<r) and arr[ny][nx] not in visit_al:
            visit_al.add(arr[ny][nx])
            DFS(nx, ny, k+1)
            visit_al.remove(arr[ny][nx])

visit_al.add(arr[0][0])
DFS(0,0,1)
print(max_num)
  • 백트래킹 추가. 좌측 상단에서 시작한다는 날을 못봐서 수정.
  • 방문한 위치는 따로 판별 배열은 필요 없다. 어짜피 방문한 알파벳 판별 set() visit_al에 있으므로..
  • DFS를 돌고나서 알파벳은 방문 여부를 False로 바꾼다. 여기서는 set()을 이용하여 remove함.
  • 이 코드는 Python으로 하면 시간 초과. PyPy로 해야 아슬아슬 통과된다.

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

 

2573번: 빙산

첫 줄에는 이차원 배열의 행의 개수와 열의 개수를 나타내는 두 정수 N과 M이 한 개의 빈칸을 사이에 두고 주어진다. N과 M은 3 이상 300 이하이다. 그 다음 N개의 줄에는 각 줄마다 배열의 각 행을

www.acmicpc.net

import sys
import copy
sys.setrecursionlimit(10**5) 
input=sys.stdin.readline

n, m = map(int, input().split()) # 세로, 가로(행, 열)
arr= []
visited = [[False]*m for _ in range(n)] 

for _ in range(n):
    arr.append(list(map(int, input().split())))
# print(arr)

dx = [0,0,-1,1]
dy = [-1,1,0,0]

def zero_num(x, y, m, n): # 주위 0의 개수
    num = 0
    for i in range(4):
        nx = x+dx[i]
        ny = y+dy[i]
        if 0<=nx<m and 0<=ny<n and arr[ny][nx]<1:
            num+=1
    # print(x, y, num)
    return num

def DFS(x, y): 
    visited[y][x] = True
    for i in range(4):
        nx = x+dx[i]
        ny = y+dy[i]
        if (0<=nx<m) and (0<=ny<n) and not visited[ny][nx] and arr[ny][nx]>0:
            new_arr[ny][nx] -= zero_num(nx, ny, m, n)
            DFS(nx, ny)

num = 0
year = 0
while num < 2:
    visited = [[False]*m for _ in range(n)] 
    # year += 1
    num = 0
    new_arr = copy.deepcopy(arr) # 빙산은 한번에 녹아야 한다!
    for i in range(n):
        for j in range(m):
            if not visited[i][j] and arr[i][j]>0:
                num += 1
                visited[i][j]=True
                new_arr[i][j] -= zero_num(j, i, m, n)
                DFS(j, i)
                arr = new_arr

    if num == 0:
        break

    if num>1:
        break
    year += 1

if num==0:
    print(0)
else:
    print(year)
  • 일단 빙산 주위 바다(0)의 개수를 세는 zero_num() 함수를 만들어서 얼마나 녹는지 체크.
  • 이때 빙산이 녹더라도 arr 배열에 바로 적용하면 안된다. 다음 빙산이 바다로 잘못 체크할 수 있으므로 new_arr을 만들어 정보를 저장하고, DFS가 끝난 후 한번에 적용했다.
  • 나머지는...DFS이다. Python은 시간초과 나서 PyPy로 제출했다.

  • sys.setrecursionlimit(10**5) 이거는 Recursive error때문에 해줘야함.
  • 재귀 + limit + deepcopy 삼종때문에 시간 초과가 난 듯하다.
  • 아래 코드가 반복문으로 푼 DFS이다. 

https://velog.io/@e_juhee/python-%EB%B0%B1%EC%A4%80-2573-%EB%B9%99%EC%82%B0-DFS

 

[python] 백준 2573 :: 빙산 (DFS)

얼음인 곳을 기준으로 DFS 탐색을 한다.탐색을 하면서 현재 얼음과 맞닿아 있는 바다의 개수를 파악한다.탐색을 하면서 얼음인 곳을 만나면 얼음의 위치를 stack에 저장해두고,stack의 요소들을 기

velog.io


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

 

1068번: 트리

첫째 줄에 트리의 노드의 개수 N이 주어진다. N은 50보다 작거나 같은 자연수이다. 둘째 줄에는 0번 노드부터 N-1번 노드까지, 각 노드의 부모가 주어진다. 만약 부모가 없다면 (루트) -1이 주어진다

www.acmicpc.net

import sys
import copy
sys.setrecursionlimit(10**5) 
input=sys.stdin.readline

n = int(input())
arr = [[]*n for _ in range(n)]
visited = [False]*n
deleted = [] # 삭제된 노드들

num = list(map(int, input().split()))
for i in range(n):
    if num[i] == -1:
        continue
    else:
        arr[num[i]].append(i)

x = int(input())

def DFS(v):
    visited[v]=True
    for i in arr[v]:
        if not visited[i]:
            deleted.append(i)
            DFS(i)

DFS(x)
deleted.append(x)
ans = 0
for i in range(n):
    if not visited[i]:
        if len(arr[i])==0:
            ans+=1
        elif all(k in deleted for k in arr[i]): # arr[i] 에 있는 모든 노드가 삭제됐다면
            ans+=1
# print(deleted)
print(ans)
  • 먼저, DFS로 삭제 노드 + 삭제한 노드의 자식 노드들을 모두 돌아서 visited를 체크해준다.
  • 또한 삭제된 노드들은 deleted 배열에 따로 추가해주었다. 
  • 만약 visited되지 않았을때, 자식 노드가 없다면 답에 +1
  • visited되지 않았지만, 자식 노드가 있는 경우 deleted 배열에 모든 자식 노드가 있는지, 즉 자식 노드가 모두 삭제됐는지 판별한 후 ans +1

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

 

9466번: 텀 프로젝트

이번 가을학기에 '문제 해결' 강의를 신청한 학생들은 텀 프로젝트를 수행해야 한다. 프로젝트 팀원 수에는 제한이 없다. 심지어 모든 학생들이 동일한 팀의 팀원인 경우와 같이 한 팀만 있을

www.acmicpc.net

# 내 풀이
import sys
import copy
sys.setrecursionlimit(10**5) 
input=sys.stdin.readline

def DFS(v):
    visited[v] = True
    team.append(v)
    if not visited[arr[v]]:
        DFS(arr[v])
    else:
        return

n = int(input())
        
for _ in range(n):
    ans = 0
    num = int(input()) # 학생 수
    arr = [0] + list(map(int, input().split()))
    for i in range(1, num+1):
    	visited = [False]*(num+1)
        team = []
        if not visited[i]:
            DFS(i)
        if team[0]!=arr[team[-1]]:
            ans+=1
    print(ans)
  • 시간 초과....각 학생마다 계속 visited를 초기화해서 문제가 생긴 것 같다.
import sys
import copy
sys.setrecursionlimit(10**7) 
input=sys.stdin.readline

def DFS(v):
    global ans
    visited[v] = True
    team.append(v)
    if not visited[arr[v]]:
        DFS(arr[v])
    else:
        if arr[v] in team: # 팀을 이룬다면
            ans += team[team.index(arr[v]):]

n = int(input())
        
for _ in range(n):
    ans = []
    num = int(input()) # 학생 수
    arr = [0] + list(map(int, input().split()))
    visited = [False]*(num+1)


    for i in range(1, num+1):
        if not visited[i]:
            team = []
            DFS(i)
    print(num - len(ans))
  • visited는 초기화하지 않고, 팀 하나를 찾았다면 찾은 팀은 DFS를 돌리지 않는다.
  • DFS를 돌리다가 만약 방문한 번호를 만났을때, 팀을 이룬다면(team에 해당 번호가 있다면) ans에 팀을 이룬 학생들의 번호를 추가한다.
  • 총 학생 수에서 팀을 이룬 학생 수를 뺀 값 출력하기.
  • ans += team[team.index(arr[v]):] 이게 핵심이다. DFS를 돌린 전체 학생이 사이클을 형성하지 못하더라도 일부 사이클만 찾을 수 있는 코드!

# 회고

 

힘들구만...

골드부터는 시간/메모리를 신경써야 하는 문제가 나온다.

Python으로 할거면 최대한 시간 안잡아먹게 작성하거나, 반복문을 이용해 DFS를 구현하거나, 최대 Recursive 설정을 잘하기. 최후의 방법으로는 PyPy쓰기...