# ---------- Import ----------
from copy import deepcopy
import sys
input = sys.stdin.readline


# ---------- Function ----------
def Rotate_90_Degree(board):
    ret = [[0]*length for _ in range(length)]
    for r in range(length):
        for c in range(length):
            ret[c][length-1-r] = board[r][c]
    return ret


def Rotate_270_Degree(board):
    ret = [[0]*length for _ in range(length)]
    for r in range(length):
        for c in range(length):
            ret[length-1-c][r] = board[r][c]
    return ret


def Reflect(board):
    for i in range(length):
        board[i] = board[i][::-1]
    return board


def LFT(board):
    for i in range(length):
        line = list(filter(None, board[i]))
        for idx in range(len(line)-1):
            if not (line[idx] - line[idx+1]):
                line[idx] *= 2
                line[idx+1] = 0
        line = list(filter(None, line))
        line += [0] * (length - len(line))
        board[i] = line
    return board


def UP(board):
    board = Rotate_270_Degree(board)
    board = LFT(board)   
    return Rotate_90_Degree(board)


def DN(board):
    board = Rotate_90_Degree(board)
    board = LFT(board)
    return Rotate_270_Degree(board)


def RGT(board):
    board = Reflect(board)
    board = LFT(board)
    return Reflect(board)


def BoardGame2048(board, move_count):
    if move_count == 5:
        global max_block
        max_block = max(max_block, max(map(max, board)))
        return
    
    for direct in ["U", "D", "L", "R"]:
        copy_board = deepcopy(board)
        BoardGame2048(switch[direct](copy_board), move_count+1)
    return


# ---------- Main ----------
max_block = 0
switch = {"U":UP, "D":DN, "L":LFT, "R":RGT}

length = int(input())
board = [list(map(int, input().split())) for _ in range(length)]

BoardGame2048(board, 0)
print(max_block)

'PS > 백트래킹' 카테고리의 다른 글

[백준] No.1987 알파벳 完  (0) 2023.09.14
[백준] No.1987 알파벳 01  (0) 2023.09.14
[백준] No.1759_암호 만들기 完  (0) 2023.07.12
[백준] No.1759_암호 만들기 01  (1) 2023.07.12
# ---------- Import ----------
import sys
input = sys.stdin.readline

# ---------- Function ----------
def NonRecursiveDFS(x, y):
    global max_move
    
    alphabet = set([(x, y, graph[0][0])])

    while alphabet and max_move < 26:
        x, y, footprint = alphabet.pop()
        
        for dx, dy in dxy:
            nx = x + dx
            ny = y + dy
            
            if 0 <= nx < row and 0 <= ny < col\
            and graph[nx][ny] not in footprint:
                alphabet.add((nx, ny, footprint + graph[nx][ny]))
                max_move = max(max_move, len(footprint)+1)

# ---------- Main ----------
row, col = map(int, input().split())
graph = [list(input().rstrip()) for _ in range(row)]

dxy = [(-1, 0), (1, 0), (0, -1), (0, 1)]
max_move = 1

NonRecursiveDFS(0, 0)

print(max_move)

'PS > 백트래킹' 카테고리의 다른 글

[백준] No.12100 2048(Easy) 完  (0) 2023.11.30
[백준] No.1987 알파벳 01  (0) 2023.09.14
[백준] No.1759_암호 만들기 完  (0) 2023.07.12
[백준] No.1759_암호 만들기 01  (1) 2023.07.12

골드 2에서 골드 1로 승급하게 해 준 문제

 

 

1987번: 알파벳

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

www.acmicpc.net

DFS로 판을 탐색하면서 모든 경우의 수를 탐색하고, 최댓값을 찾는 문제다.

일반적인 백트래킹 문제라고 생각하여 접근했는데, 꽤나 틀려서 글을 작성한다.

제발 기본에 집중하자 기본에.

 

# ---------- Function ----------
def DFS(x, y, cur_move):
    global max_move
    max_move = max(max_move, cur_move)
    
    for dx, dy in dxy:
        nx = x + dx
        ny = y + dy
        
        if 0 <= nx < row and 0 <= ny < col and graph[nx][ny] not in alphabet:
            alphabet.add(graph[nx][ny])
            DFS(nx, ny, cur_move+1)
            alphabet.remove(graph[nx][ny])
            
# ---------- Main ----------
row, col = map(int, input().split())
graph = [list(input().rstrip()) for _ in range(row)]

alphabet = set()
alphabet.add(graph[0][0])

dxy = [(-1, 0), (1, 0), (0, -1), (0, 1)]

max_move = 1
DFS(0, 0, 1)

print(max_move)

처음에 작성하고 조금 다듬은 코드이다.

골드 4에 백트래킹, 시간 제한 2초에 정답 비율 29.316%을 보고 시간에 유의해야 겠다 생각했다.

접근법은 이러하다.

우선 지나는 알파벳들의 종류는 중복해서 지나서는 안 된다. 또한 시간에 유의해야 한다.

list로 저장해서 not in list로 확인할 수 있지만, 이건 시간복잡도가 O(n)이 걸린다.

하지만 set은 중복을 피하는 저장이 가능하고 not in set을 사용하면 시간복잡도가 O(1)이다.

이점을 감안하여 처음부터 set() 자료구조를 선택했다.

 

visited로 간 곳을 확인하려고 했지만, 의미없는 일이었다.

가장 처음에는 (graph[nx][ny] not in alphabet) and (visited[nx][ny] == 0)으로 작성했다.

하지만 생각해보니 set()만 확인해주면 되는 일이었다.

가보지 않은 곳일 경우, 중복한 값이라면 어차피 못 가고, 중복되지 않다면 간다.

다시 말해 결정 사항은 중복 여부에 따라서 정해진다는 것이다.

 

그리고 마지막으로 dxy에 대해서 고민했다.

전에 백준 보물섬 문제를 풀 때 겪었던 상황이었다.

PyPy3로 제출하면 AC를 받는데, Python3로 제출하면 TLE가 뜨는 상황이었다.

무엇이 문제일까 생각하던 중 list 접근은 O(n)이니까 dx, dy 접근에서 시간이 쌓이나?하는 의문이 들었다.

이와 비슷하게 백준 마인크래프트 문제를 풀 때 if-else가 아닌 if-if를 쓴 것 때문에 TLE가 난 적이 있었다.

그래서 dx, dy를 각각 방향값을 넣어 index로 찾지 않고, 튜플로 선언하여 직접 값을 받았다.

 └ dx = [-1, 1, 0, 0], dy = [0, 0, -1, 0]이 아니라 dxy = [(-1, 0), (1, 0), (0, -1), (0, 1)]로 사용했다.

그렇게 Python3로 제출하니 TLE가 안 나고 성공적으로 문제를 풀었던 적이 있다.

따라서 dx, dy가 아닌 dxy로 직접 값을 받아서 사용했다.

 

여러 방면으로 시간 초과를 고려하여 작성했음에도 피할 수 없었다.

혹시나 하는 마음에 PyPy3로 제출해보았다.

 

이런... PyPy3는 통과한다...

우선 구조는 맞았다는 이야기다. 그래도 한시름 놓았다. 틀린 코드를 짠 게 아니구나.

 

if max_move == 26:
    return

백준 테트로미노 문제를 풀 때 겪었던 상황이었다.

이와 유사하지만 더 어려운 백트래킹 문제였는데, 자꾸 TLE가 떴다.

그래서 생각을 해보다가 종료 조건문을 넣어주었던 적이 있다.

이번 문제에서도 이런 종료 조건을 적용할 수 있을 것 같았다.

 

알파벳 중복을 피해서 간다면 갈 수 있는 최대 경로는 26이다.

그래서 max_move가 26이라면 더 탐색을 할 필요가 없다.

 

그랬더니 1%도 올라가지 않던 채점 현황이 1%나 올라갔다!

1조에서 1%면 무려 100억이다. 기업 입장에서는 크다. 그래 조금씩 맞춰가면 된다.

한 번 바꿀 때마다 1%면 100번만 하면 되지 않은가.

 

if max_move == 26 or (row * col) - cur_move <= max_move:
    return

26이 아니라 한 가지 조건을 더 넣어주었다.

남아있는 칸((row * col) - cur_move)이 max_move보다 작다면 굳이 더 이동할 필요가 없다.

그러니까, 남아있는 칸을 전부 탐색해도 max_move보다 작으면 탐색의 의미가 없다는 말이다.

그런 생각으로 위의 식을 작성해주었다.

 

6% 채점까지 더 늘어났다. 더 생각해보자.

 

if (row * col) - cur_move <= max_move or max_move == 26:
    return

생각해보면 26이 되는 경우보다, 가지 못하게 되는 경우가 더 많지 않을까?

이런 생각으로 위의 두 조건식의 순서를 바꿔주었다. 

 

지금 작성하면서 드는 생각이지만, 애초에 저 조건식을 잘못 짰다.

우연히 저 경우에 맞아떨어지면서, 마치 저 식이 맞은 듯한 상황이 된 것이다.

채점할 때도 %가 올라가서 맞나...? 싶었는데 아니다.

 

어쨌든 8%로 더 높아지긴 했지만, 조금 사고를 바꿀 필요가 있다. 애초에 식도 틀렸고 말이다.

너무 작은 거에 얽매이고 있다. 큰 틀에서 바라봐보자.

현재 DFS를 이용한 백트래킹으로 코드를 구현했고, TLE가 뜬다.

DFS와 백트래킹, 그리고 TLE.

어라? 그럼 비재귀 DFS로 짜면 되지 않나?

 

alphabet = set([(x, y, graph[0][0])])

생각해보면 갈 수 있는 최대 경우의 수는 26이다.

모든 경로를 저장한다고 가정했을 때, 문자열 26개, 좌표를 나타내는 숫자 2개.

그리고 이런 저장 공간이 26개 있다고 할 때, 계산해보면 1MB도 사용하지 않는다.

정확하게는 680B를 사용한다.

 └ Python3에서 list는 기본 56B, 3개의 주소 공간을 할당하는데 각 8B해서 80B다. 80 * 26 = 680

문제 조건의 256MB가 차고 넘칠 정도로 많이 남는다.

 

# ---------- Function ----------
def NonRecursiveDFS(x, y):
    global max_move
    
    alphabet = set([(x, y, graph[0][0])])

    while alphabet and max_move < 26:
        x, y, footprint = alphabet.pop()
        
        for dx, dy in dxy:
            nx = x + dx
            ny = y + dy
            
            if 0 <= nx < row and 0 <= ny < col and graph[nx][ny] not in footprint:
                alphabet.add((nx, ny, footprint + graph[nx][ny]))
                max_move = max(max_move, len(footprint)+1)

그리고 백트래킹으로 값을 넣고 뺀다면, 가장 첫 혹은 마지막 값에만 접근한다.

즉 0번째 값 아니면 -1번째 값이다. 이건 list로 탐색해도 무조건 O(1)이다.

 

그래서 alphabet에 좌표와 여태껏 이동한 발자취(footprint)를 저장한다.

alphabet이 빌 때까지 계속 돌고, 혹은 max_move가 26이 되는 순간 종료한다.

4방위를 탐색하면서 범위 내에 있고, footprint에 그 값이 없다면,

alphabet에 nx, ny 좌표와 현재 footprint에 graph[nx][ny]를 이은 값을 저장한다.

그리고 max_move를 업데이트한다.

 

종료 조건문은 따로 작성해주지 않아도 상관없다.

조건에 맞는 모든 구역을 다 돌면 종료하거나(while alphabet), 조기 종료(max_move < 26)한다.

 

그렇게 맞았다. 문제 풀이에 걸린 시간은 대략 45분 정도 걸렸다.

문제 해결 능력은 올라간다는 느낌이 들기는 하는데 아직도 부족하다.

문제를 잘 푼다는 게 기초가 충분하다는 사실과는 별개니까.

또한, '잘' 푼다지 '빨리' 푼다는 아니다. 코딩 테스트에서는 여유 시간을 생각해서 30분에는 끝내야 한다.

타이머를 맞춰두고 푸는 연습을 조금 더 해야 겠다.

상황에 더 몰입해야 한다.

'PS > 백트래킹' 카테고리의 다른 글

[백준] No.12100 2048(Easy) 完  (0) 2023.11.30
[백준] No.1987 알파벳 完  (0) 2023.09.14
[백준] No.1759_암호 만들기 完  (0) 2023.07.12
[백준] No.1759_암호 만들기 01  (1) 2023.07.12
# ---------- Import ----------
from itertools import combinations
import sys
input = sys.stdin.readline

# ---------- Main ----------
makeLength, inputLength = map(int, input().split())
text = input().rstrip().split()
text.sort()

for isRight in list(combinations(text, makeLength)):
    aeiou = 0
    aeiouNOT = 0
    
    for check in isRight:
        if check in ["a", "e", "i", "o", "u"]:
            aeiou += 1
        else:
            aeiouNOT += 1
            
    if aeiou > 0 and aeiouNOT > 1:
        print(''.join(isRight)) 
        
# ---------- Comment ----------
# 조건: 3글자 이상이고, 모음이 최소 1개 이상
# 따라서 자동으로 자음 2개 이상을 충족하리라 생각
# 하지만 모음 2개, 3개인 경우에는 미충족, 따라서 자음도 세줘야 함

'PS > 백트래킹' 카테고리의 다른 글

[백준] No.12100 2048(Easy) 完  (0) 2023.11.30
[백준] No.1987 알파벳 完  (0) 2023.09.14
[백준] No.1987 알파벳 01  (0) 2023.09.14
[백준] No.1759_암호 만들기 01  (1) 2023.07.12

 

 

1759번: 암호 만들기

첫째 줄에 두 정수 L, C가 주어진다. (3 ≤ L ≤ C ≤ 15) 다음 줄에는 C개의 문자들이 공백으로 구분되어 주어진다. 주어지는 문자들은 알파벳 소문자이며, 중복되는 것은 없다.

www.acmicpc.net

입력받은 문자에 모음이 있는지 확인해보고, 정해진 길이만큼 출력하는 문제이다.

처음 봤을 때, python의 combinations 라이브러리로 해결할 수 있을 것 같았다.

보통 combinations로 푸는 문제들은 백트래킹에 해당하는데, 혹시나 했는데 역시나였다.

가능한 모든 경우의 수를 다 돌아야 하기 때문에 브루트포스 문제도 당연했다.

 └ 실은 코테를 준비하다보니 브루트포스 문제가 많이 보여서 연습하려고 브루트포스 카테고리에서 골랐다.

 └ 신입의 문제는 불필요한 시간과 메모리 낭비, 즉 더티 코드일 가능성이 높으니 브루트포스를 자주 물어보는 것 같다.

 

골드 5 문제치고는 쉽다고 생각했다.

그냥 조합해보고 모음이 있는지 없는지 확인만 하면 되는 거 아닌가? 생각했다.

 

# ---------- Import ----------
from itertools import combinations
import sys
input = sys.stdin.readline

# ---------- Main ----------
makeLength, inputLength = map(int, input().split())
text = input().rstrip().split()
text.sort()

vowels = ["a", "e", "i", "o", "u"]

for v in list(combinations(text, makeLength)):
    for check in vowels:
        if v in check:
            print(''.join(v))
            continue

처음에 작성한 코드이다.

모음(vowel) 리스트를 만들어놓고, combinations list와 비교한다.

그리고 join 구문을 활용하여 공백을 제거하여 출력하려고 했다.

 

    if v in check:
       ^^^^^^^^^^
TypeError: 'in <string>' requires string as left operand, not tuple

TypeError가 발생했다. 코드를 살펴보니 반대로 작성했다.

v가 list에서 확인한 조합들이라, 예를 들면 ["a", "f", "h"] 같은 리스트이다.

check는 vowels의 하나 원소들인 "a", "e", "i" 등이다.

if v in check가 아니라 if check in v가 되어야 했다.

 

수정을 했음에도 틀렸습니다가 떠서 확인을 해보았다.

한 번씩만 출력을 해야 하는데 여러 번 출력을 했다.

정확하게는 조합한 단어에 들어있는 모음의 개수만큼 출력을 했다.

분명 for 문에서 빠져나오게 했는데...? continue를 썼구나 아하...

 

그래도 틀렸다. 코드 상으로는 틀린 게 없다고 생각했다.

그래서 문제 조건을 또 빼먹었구나 싶어서 살펴보았다.

최소 한 개의 모음과 최소 두 개의 자음, 그리고 조합하는 최소  문자열의 길이는 3이다.

이 조건은 주의했었지만 크게 신경 쓸 필요가 없다고 생각했다.

왜냐하면 최소 길이가 3일 때, 모음이 1개라도 들어가면 자동으로 나머지는 자음이 2개가 되니까 말이다.

 

그래서 생각했다.

내 코드는 모음이 1개라도 들어가게 동작하는 코드가 맞나?

정말 최소한 1개만 있도록 확인하는 코드가 맞나?

아니었다. 1개가 있으면 break로 빠져나올 뿐, 1개만 있다고 보장하지는 않는다.

그러니까 모음만 4개가 있어도 출력을 했다는 것이다. 이는 자음 최소 조건을 충족하지 않았다.

조건 하나가 허투루 쓰이지를 않는다.

 

# ---------- Import ----------
from itertools import combinations
import sys
input = sys.stdin.readline

# ---------- Main ----------
makeLength, inputLength = map(int, input().split())
text = input().rstrip().split()
text.sort()

for isRight in list(combinations(text, makeLength)):
    aeiou = 0
    aeiouNOT = 0
    
    for check in ["a", "e", "i", "o", "u"]:
        if check in isRight:
            aeiou += 1
        else:
            aeiouNOT += 1
            
    if aeiou > 0 and aeiouNOT > 1:
        print(''.join(isRight))

vowels라는 변수가 직관적이지 않아서 aeiou로 바꿨다.

aeiou는 모음, aeiouNOT은 자음을 세는 변수이다.

 

또 틀렸길래 다시 한 번 값을 확인해줬다.

? 왜...? 모음하고 자음값이 이상하지? 가만보니 두 개의 합이 전부 5다.

4가 아니라 5인 이유가 있을텐데...하고 가만보니 for문과 if문의 조건을 잘못 지정해줬다.

5가 왜 튀어나왔을까 하고 봤더니, ["a", "e", "i", "o", "u"]의 길이였다.

["a", "e", "i", "o", "u"]를 돌면서 combinations한 문자열에 있나없나 확인을 한 코드였다.

반대가 되어야 한다. combinations한 문자열에 ["a", "e", "i", "o", "u"]의 유무를 봐야 한다.

 

# ---------- Import ----------
from itertools import combinations
import sys
input = sys.stdin.readline

# ---------- Main ----------
makeLength, inputLength = map(int, input().split())
text = input().rstrip().split()
text.sort()

for isRight in list(combinations(text, makeLength)):
    aeiou = 0
    aeiouNOT = 0
    
    for check in isRight:
        if check in ["a", "e", "i", "o", "u"]:
            aeiou += 1
        else:
            aeiouNOT += 1
            
    if aeiou > 0 and aeiouNOT > 1:
        print(''.join(isRight))

최종적으로 작성한 코드이다. 조건 하나하나 허투루 쓰지 않아야 한다.

물론 내가 작성한 코드들의 조건문도 잘 확인해야 한다.

그리고 답이 틀렸으면 내 코드에서 분명히 틀린 부분이 있을 것이다.

천천히 생각해보면서 추적해나가면 금방 이유를 찾아낼 수 있다.

 

그럼 성공한다.

 

P.S.1

문제를 이해할 때 해석에 오류가 있었다.

문제를 풀 때는 상관이 없어서 큰 문제는 아니지만, 오해의 소지가있을 수 있어서 질의했다.

이런... 평소에 최대한 다양하게 해석하고 여러 방면에서 읽으려는 것이 여기서 또 문제였다.

생각을 줄여야 하나 싶기도 한데, 딜레마다. 생각이 너무 많다.

이래서 코드 오류를 금방 찾기는 하지만... 잘 모르겠다.

 

해석에 도움을 준 천상계 jh05013님께 감사를 표한다. (현시점 9위다)

나도 남에게 도움을 줄 수 있는 위치까지 얼른 올라가고 싶다.

'PS > 백트래킹' 카테고리의 다른 글

[백준] No.12100 2048(Easy) 完  (0) 2023.11.30
[백준] No.1987 알파벳 完  (0) 2023.09.14
[백준] No.1987 알파벳 01  (0) 2023.09.14
[백준] No.1759_암호 만들기 完  (0) 2023.07.12

+ Recent posts