http://boj.kr/2252 

 

2252번: 줄 세우기

첫째 줄에 N(1≤N≤32,000), M(1≤M≤100,000)이 주어진다. M은 키를 비교한 회수이다. 다음 M개의 줄에는 키를 비교한 두 학생의 번호 A, B가 주어진다. 이는 학생 A가 학생 B의 앞에 서야 한다는 의미이다. 학생들의 번호는 1번부터 N번이다.

www.acmicpc.net

 

 

위상 정렬 기본 문제이다.

위상 정렬이 무엇인가 하면, 어떤 유향 그래프가 있을 때 선후 관계를 해치지 않도록 노드를 정렬하는 것이다. 

당연하게도 이 유향 그래프에는 사이클이 있으면 안 된다. 사이클이 있으면 선후 관계라는 게 있을 수가 없기 때문이다.

 

위상 정렬을 하는 방법은 두 가지가 있다. 하나는 재귀를 사용하는 방법, 다른 하나는 재귀를 사용하는 방법이다.

조금 더 직관적이지만 구현이 복잡한 노 재귀 방법부터 보자.

 

 

다음과 같은 사이클이 없는 유향그래프가 있고, 정렬된 배열을 S라고 하자.

첫 번째 방법은 한 마디로 설명하면 '시작 노드를 그래프에서 빼서 순서대로 놓는 것'이다.

시작노드들이란 그 노드로 들어오는 간선이 없는 노드들을 말한다.

즉 인접 리스트 등에서 어떤 노드보다 뒤에 있다고 설명되지 않는 노드들이다.

초기 그래프와 정렬된(텅 빈) 배열 S이다.

여기서 시작노드들은 1과 5이다. 노드 1이나 5로 들어오는 간선이 없기 때문에 1과 5는 시작 노드이다.

위상 정렬을 하기 위해서는 이 시작 노드 중 하나를 선택해서, 그 노드와 그 노드로부터 출발하는 진출 간선을 모두 삭제한다. 1을 선택해보자.

 

 

 

 

 

1과 1의 진출 간선이 삭제되었다. 그리고 1이 정렬된 배열 S의 맨 뒤에 추가되었다. 

간단하게도 이것을 계속 반복하면 된다.

삭제된 1의 진출 간선과 연결되어 있던 2는 진입 간선을 하나 잃었다. (1의 진출 간선=2의 진입 간선)

이제 진입 간선이 없는 시작 노드는 2, 5가 되었다. 다시 그 중에서 하나를 선택해 그 노드와 진출 간선을 모두 삭제한다. 이번에는 2를 선택해보자.

 

 

 

 

2를 삭제하고 2로부터 출발하는 진출 간선들을 모두 삭제했다. 그리고 2를 정렬된 배열 S의 맨 뒤에 추가했다.

이제 시작 노드는 5 하나이다.

 

 

 

5를 삭제하고 배열 S에 추가했고, 5로부터 출발하는 5의 진출 간선들을 모두 삭제했다. 

이제 시작 노드는 4와 6이다. 이 중 4를 선택해보자.

 

 

 

 

4를 삭제하고 배열 S에 추가한 뒤, 4의 진출 간선들을 모두 삭제했다. 

이제 시작 노드는 6 하나 뿐이다.

 

 

 

6을 삭제하고 배열 S에 추가했다. 그리고 배열 S에 추가했다.

이제 시작 노드는 3 하나 뿐이다.

 

 

3을 삭제하고 배열 S의 맨 뒤에 추가했다. 이제 남은 노드가 없으므로 배열 S가 위상 정렬이 완료된 배열이 되었다.

 

 

import sys


def solution():
    for _ in range(N):
        u = start_v.pop()  # 시작 노드 중 하나를 꺼낸다
        answer.append(u+1)  # 배열의 맨 뒤에 삽입
        for node in out[u]:  # 노드 u와 연결된 간선들을 삭제하면 그 간선들과 연결된 노드들의 진입 간선 수가 줄어든다
            in_num[node] -= 1
            if in_num[node] == 0:  # 진입 간선이 0이 된 간선은 시작 노드 배열에 추가
                start_v.append(node)


answer = []
input = sys.stdin.readline
N, M = map(int, input().split())  # N = 학생수, M = 비교횟수
start_v = []  # 진입 간선이 0인 시작 노드
in_num = [0 for _ in range(N)]  # 진입 간선의 수. in_num=0인 노드가 시작 노드가 된다

out = [[] for _ in range(N)]  # 모든 노드에 대해 선후 관계를 정리한 배열

# 노드들의 선후 관계가 입력으로 들어온다. 배열의 인덱스는 0부터 시작이므로 각각 1씩 마이너스해서 배열에 담는다.
for _ in range(M):
    a, b = map(int, input().split())
    out[a-1].append(b-1)
    in_num[b-1] += 1

for idx, num in enumerate(in_num):
    if num == 0:
        start_v.append(idx)  # 진입간선이 0인 정점들은 먼저 줄세운다

solution()

for a in answer:
    print(a, end=' ')

위상정렬.pptx
0.05MB

 

 

 

두 번째 방법은 재귀를 이용하는 것이다. 모습이 DFS를 닮았기 때문에 DFS 방법이라고 한다.

이번에는 아무 노드나 선택한 후, 그 노드와 연결된 노드들을 재귀로 찾아서 배열의 맨 앞에 삽입하면 된다.

import sys
from collections import deque


def solution():
    for i in range(N):
        if not visited[i]:
            dfs(i)


def dfs(v):
    visited[v] = True
    for next_v in out[v]:
        if not visited[next_v]:
            dfs(next_v)
    answer.appendleft(v+1)


answer = deque()
input = sys.stdin.readline
N, M = map(int, input().split())  # N = 학생수, M = 비교횟수
visited = [False for _ in range(N)]
out = [[] for _ in range(N)]  # 인접 리스트. 진출 간선들을 정리한 리스트.
for _ in range(M):
    a, b = map(int, input().split())
    out[a-1].append(b-1)
    
solution()
for a in answer:
    print(a, end=' ')

 

http://boj.kr/16637 

 

16637번: 괄호 추가하기

길이가 N인 수식이 있다. 수식은 0보다 크거나 같고, 9보다 작거나 같은 정수와 연산자(+, -, ×)로 이루어져 있다. 연산자 우선순위는 모두 동일하기 때문에, 수식을 계산할 때는 왼쪽에서부터 순서대로 계산해야 한다. 예를 들어, 3+8×7-9×2의 결과는 136이다. 수식에 괄호를 추가하면, 괄호 안에 들어있는 식은 먼저 계산해야 한다. 단, 괄호 안에는 연산자가 하나만 들어 있어야 한다. 예를 들어, 3+8×7-9×2에 괄호를 3+(8×7)-(9×

www.acmicpc.net

 

 

모든 경우를 살펴야 하는 완전탐색 문제이다. 각 숫자를 처음부터 보면서, 이번 숫자가 먼저 계산되는 경우괄호 때문에 나중에 계산되는 경우를 각각 나눠서 재귀를 호출하면 모든 경우를 다 볼 수 있다.

1+2*3 가 있다고 하고, 이번 숫자는 1이라고 해보자. 1이 먼저 계산되는 경우는 (1+2)*3 일 것이고, 먼저 계산되지 않는 경우는 괄호가 뒤에 있어서 1+(2*3)인 경우일 것이다. 모든 숫자에 대해서 가능한 케이스는 먼저 계산되는 케이스이거나, 괄호 때문에 나중에 계산되는 케이스가 전부이므로 이렇게 두 경우를 보면 모든 케이스를 다 살펴볼 수 있다.

 

재귀함수의 매개변수는 idx, ret인데 idx는 연산자의 인덱스를 말한다. 왜 갑자기 연산자의 인덱스를 보냐?고 할 수도 있는데, 뭐 숫자의 인덱스나 연산자의 인덱스나 똑같다. ret는 지금까지 계산된 결과이다. 문제에서 식은 순서대로 계산되며, 두 번 괄호가 쳐질 수 없다고 했으므로 앞에서부터 ret을 계산하면 된다.

 

다시 돌아가서, 이번 숫자가 먼저 계산되는 경우, 그러니까 (1+2)*3일 때는 사실 순서대로 계산하면 된다. 이번 숫자가 1이고 다음 숫자가 2이므로, 1과 2를 더하면 된다. 그리고 재귀함수를 호출할 때는 이번 숫자에 대한 계산이 끝났으므로 idx를 1 증가시켜서 호출하면 된다.

 

나중에 계산되는 경우, 그러니까 1+(2*3)같은 경우는 2*3을 먼저 계산한 후, 1+(2*3의 결과)를 다시 계산해서 그 다음에 그 결과를 가지고 재귀를 호출한다. 여기서 결과라는건 계산의 결과인 ret을 의미한다. 여기서 주의해야 할 점은 이번에 본 숫자는 1인데 2칸 앞서 나가서 3까지 계산해버렸으므로, 다음 idx는 2를 더한 idx+2가 될 것이다. 

 

import sys


def divide_num_operator(string, num, operator):
    for i in string[:-1]:
        if i.isdigit():
            num.append(int(i))
        else:
            operator.append(i)


def calc(n1, n2, operator):
    if operator == '*':
        return n1 * n2
    elif operator == '+':
        return n1 + n2
    elif operator == '-':
        return n1 - n2


def dfs(idx, ret):
    global max_sum
    if idx >= len(operator):
        max_sum = max(max_sum, ret)
        return

    # 이번에 계산 되는 경우
    dfs(idx+1, calc(ret, num[idx+1], operator[idx]))

    # 이번에 계산되지 않는 경우->뒤에가 먼저 계산되는 경우(괄호가 나보다 뒤에 있음)
    if idx+1 < len(operator):
        dfs(idx+2, calc(ret, calc(num[idx+1], num[idx+2], operator[idx+1]), operator[idx]))


def solution(arr):
    divide_num_operator(arr, num, operator)

    dfs(0, num[0])


max_sum = float('-inf')
input = sys.stdin.readline
N = int(input())
arr = input()
num, operator = [], []
solution(arr)
print(max_sum)

'알고리즘 > 브루트포스' 카테고리의 다른 글

소수 찾기 (Python)  (0) 2020.07.04
16637. 괄호 추가하기 (2)  (0) 2020.05.12
12100번. 2048 (Easy)  (0) 2020.05.04
17609번. 회문  (0) 2020.04.19
12100. 2048 (Easy)  (0) 2020.01.07

http://boj.kr/3190 

 

3190번: 뱀

문제  'Dummy' 라는 도스게임이 있다. 이 게임에는 뱀이 나와서 기어다니는데, 사과를 먹으면 뱀 길이가 늘어난다. 뱀이 이리저리 기어다니다가 벽 또는 자기자신의 몸과 부딪히면 게임이 끝난다. 게임은 NxN 정사각 보드위에서 진행되고, 몇몇 칸에는 사과가 놓여져 있다. 보드의 상하좌우 끝에 벽이 있다. 게임이 시작할때 뱀은 맨위 맨좌측에 위치하고 뱀의 길이는 1 이다. 뱀은 처음에 오른쪽을 향한다. 뱀은 매 초마다 이동을 하는데 다음과 같은 규칙을 따

www.acmicpc.net

 

 

'뱀'은 머리가 붙었다가 꼬리가 떼졌다가 즉 앞뒤로 데이터를 넣었다가 뺐다가 해야 하는 이유로 deque를 사용했다.

board에서 뱀은 2, 사과는 1, 나머지는 0으로 표시했다.

 

move 리스트에 담은 L개의 방향 전환 정보는 '게임 시작 후 X초가 지난 후에 방향을 전환한다'는 뜻이다. 

 

time을 증가시켜가면서 매 초마다 move 안에 담긴 X초와 같은지 비교하고, 같으면 방향을 바꿨다. 

그리고 그렇게 방향을 전환했을 때마다 move 리스트의 인덱스를 가리키는 idx를 1씩 증가시켰다.

방향 전환이 끝나도 뱀은 계속해서 이동하므로 while True: 안에서 이동하도록 해 주었다.

import sys
from collections import deque


def Print(lst):
    for i in lst:
        for j in i:
            print(j, end=' ')
        print("")
    print("")


def get_next_direction(now, rotate):  # (현재 방향, 움직임 명령(L or D))
    if now == 0:
        if rotate == 'L':
            return 2
        else:
            return 3
    elif now == 1:
        if rotate == 'L':
            return 3
        else:
            return 2
    elif now == 2:
        if rotate == 'L':
            return 1
        else:
            return 0
    elif now == 3:
        if rotate == 'L':
            return 0
        else:
            return 1


def solve():
    time = 0
    idx = 0  # move의 인덱스
    direct = 0  # 초기 방향 -> (0)
    while True:
        time += 1
        next_r, next_c = snake[0][R] + dx[direct], snake[0][C] + dy[direct]

        if 0 <= next_r < N and 0 <= next_c < N:
            if board[next_r][next_c] == SNAKE:  # 몸에 부딪힘
                return time
            snake.appendleft([next_r, next_c])  # 새로운 머리
            if board[next_r][next_c] != APPLE:
                tail_r, tail_c = snake.pop()
                board[tail_r][tail_c] = 0
            board[next_r][next_c] = SNAKE
        else:  # 벽에 부딪힘
            return time

        if idx < L:
            if time == move[idx][0]:
                direct = get_next_direction(direct, move[idx][1])  # 현재 방향, 다음 회전 명령
                idx += 1
                

dx = (0, 0, -1, 1)
dy = (1, -1, 0, 0)

R, C = 0, 1
APPLE, SNAKE = 1, 2
input = sys.stdin.readline
N = int(input())

board = [[0 for _ in range(N)] for _ in range(N)]
board[0][0] = 2  # 처음에 뱀은 (0, 0)에 위치하고 있음
K = int(input())
for _ in range(K):
    i, j = map(int, input().split())
    board[i-1][j-1] = APPLE

L = int(input())  # 뱀의 방향 전환 정보

move = []
for _ in range(L):
    i, j = input().split()
    move.append([int(i), j])

snake = deque([[0, 0]])  # row, col
print(solve())

'알고리즘 > 시뮬레이션' 카테고리의 다른 글

17837번. 새로운 게임 2  (0) 2020.05.03
17822번. 원판 돌리기  (0) 2020.05.02
17140번. 이차원 배열과 연산  (0) 2020.04.27
SWEA 2382번. 미생물 격리  (0) 2020.04.24
17144번. 미세먼지 안녕!  (0) 2020.04.21

http://boj.kr/12100 

 

12100번: 2048 (Easy)

첫째 줄에 보드의 크기 N (1 ≤ N ≤ 20)이 주어진다. 둘째 줄부터 N개의 줄에는 게임판의 초기 상태가 주어진다. 0은 빈 칸을 나타내며, 이외의 값은 모두 블록을 나타낸다. 블록에 쓰여 있는 수는 2보다 크거나 같고, 1024보다 작거나 같은 2의 제곱꼴이다. 블록은 적어도 하나 주어진다.

www.acmicpc.net

 

 

보드는 동서남북으로 기울일 수 있으므로 임의로 각 방향마다 0~4까지 번호를 정해주었다. 

그리고 미리 가능한 방향들의 경우의 수를 모두 구한 후에 그대로 기울여본 후 최대값을 갱신하는 방식으로 진행했다.

가능한 방향들의 경우의 수를 구하는 방법은, 뽑을 수 있는 가짓수(방향)는 4개이고 총 5개를 뽑아야 하므로 4개 중 5개를 중복을 허용하는 순열로 뽑았다. 기울이는 순서에 따라 결과도 달라지므로 조합이 아니라 순열로 뽑아야 한다.

 

한 가지 방향으로 기울이는 방법만 알면 나머지 방향은 조금씩만 수정하면 되므로, 위로 기울이는 방법을 보자.

위로 기울인다는 것은 (0, 0) 원소 입장에서 (1, 0) 에 있는 원소들을 땡겨온다는 뜻이다. 

여기서 두 케이스가 있을 수 있다. 기준 원소 (0, 0)의 값이 0인 케이스와 0이 아닌 케이스가 있을 수 있다.

먼저 값이 0이면 다음 행에 있는 것들 중 0이 아닌 값을 찾아서 가져와야 한다. 즉, 먼저 밀어주어야 한다. 

 

이제 기준 원소의 값이 0이 아니게 되었으면, 거기서부터 다시 그 다음 행에서 0이 아닌 값들을 가져와서 비교한다.

비교해서 같은 값이면 합쳐준다. 다른 값이면 아무 행동도 하지 않는다. 어차피 다음 반복문에서 그 원소를 땡겨오거나 사용하게 될 것이다.

 

남쪽으로 밀어줄 때는 기준 원소가 0에서 시작하는 것이 아니라 N-1에서 시작해서 1로 끝난다는 점이 달라진다.

마지막이 (1, y)가 되고 비교 원소가 (0, y)가 될 것이므로 마지막 row가 0이 아닌 1이 된다. 마지막 row를 0으로 두면 그 다음 비교 원소가 -1이 되므로, out of index가 되거나 비교문을 한번 넣어줘야 한다.

 

주의해야 할 점은 한 번의 기울임마다 두 번 합쳐질 수 없다는 뜻이지, 한번 합쳐진 원소는 두번 다시 못 합쳐진다는 뜻이 아니므로 한번 기울여진 적이 있음을 체크하는 배열을 계속 초기화해줘야 된다는 점이다. (여기서는 added를 계속 새로 선언했다.)

 

import sys
from copy import deepcopy


def solve(selected):
    board = deepcopy(board_o)
    for direct in selected:
        added = [[False for _ in range(N)] for _ in range(N)]
        if direct == 0:  # UP
            for r in range(N-1):
                for c in range(N):
                    next_r = r + 1
                    
                    # 기준 원소의 값이 0일 때 -> 0이 아닌 값을 찾아서 땡겨온다.
                    if board[r][c] == 0:
                        while next_r < N:
                            if board[next_r][c] == 0:
                                next_r += 1
                            else:
                                board[r][c] = board[next_r][c]
                                board[next_r][c] = 0
                                break
                                
                    # 기준 원소의 값이 0이 아닐 때 -> 다음 줄에 있는 원소 중 0이 아닌 것을 땡겨온다.
                    while next_r < N:
                        if board[next_r][c] == 0:
                            next_r += 1
                        else:  # 0이 아닌 것을 땡겨 왔으면 비교 후 같으면 합쳐준다.
                            if board[r][c] == board[next_r][c] \
                                    and not added[next_r][c] and not added[r][c]:
                                board[r][c] = board[r][c] * 2
                                board[next_r][c] = 0
                                added[r][c] = True
                                added[next_r][c] = False
                            break
        elif direct == 1:  # DOWN
            for r in reversed(range(1, N)):
                for c in range(N):
                    next_r = r - 1
                    if board[r][c] == 0:
                        while next_r >= 0:
                            if board[next_r][c] == 0:
                                next_r -= 1
                            else:
                                board[r][c] = board[next_r][c]
                                board[next_r][c] = 0
                                break
                    while next_r >= 0:
                        if board[next_r][c] == 0:
                            next_r -= 1
                        else:
                            if board[r][c] == board[next_r][c] \
                                    and not added[next_r][c] and not added[r][c]:
                                board[r][c] = board[r][c] * 2
                                board[next_r][c] = 0
                                added[r][c] = True
                                added[next_r][c] = False
                            break
        elif direct == 2:  # LEFT
            for r in range(N):
                for c in range(N - 1):
                    next_c = c + 1
                    if board[r][c] == 0:
                        while next_c < N:
                            if board[r][next_c] == 0:
                                next_c += 1
                            else:
                                board[r][c] = board[r][next_c]
                                board[r][next_c] = 0
                                break
                    while next_c < N:
                        if board[r][next_c] == 0:
                            next_c += 1
                        else:
                            if board[r][c] == board[r][next_c] \
                                    and not added[r][next_c] and not added[r][c]:
                                board[r][c] = board[r][c] * 2
                                board[r][next_c] = 0
                                added[r][c] = True
                                added[r][next_c] = False
                            break
        elif direct == 3:  # Right
            for r in range(N):
                for c in reversed(range(1, N)):
                    next_c = c - 1
                    if board[r][c] == 0:  # 땡기기
                        while next_c >= 0:
                            if board[r][next_c] == 0:
                                next_c -= 1
                            else:
                                board[r][c] = board[r][next_c]
                                board[r][next_c] = 0
                                break
                    while next_c >= 0:
                        if board[r][next_c] == 0:
                            next_c -= 1
                        else:
                            if board[r][c] == board[r][next_c] \
                                    and not added[r][next_c] and not added[r][c]:
                                board[r][c] = board[r][c] * 2
                                board[r][next_c] = 0
                                added[r][c] = True
                                added[r][next_c] = False
                            break

    ret = max([max(r) for r in board])
    return ret


def dfs(count, selected):
    global maxRet
    if count == 5:
        maxRet = max(maxRet, solve(selected))
        return
    for i in range(4):
        dfs(count+1, selected+[i])
    return maxRet


maxRet = 0
input = sys.stdin.readline
N = int(input())
board_o = [list(map(int, input().split())) for _ in range(N)]
print(dfs(0, []))

 

'알고리즘 > 브루트포스' 카테고리의 다른 글

16637. 괄호 추가하기 (2)  (0) 2020.05.12
16637번. 괄호 추가하기  (0) 2020.05.11
17609번. 회문  (0) 2020.04.19
12100. 2048 (Easy)  (0) 2020.01.07
2231. 분해합  (0) 2019.11.20

http://boj.kr/17837 

 

17837번: 새로운 게임 2

재현이는 주변을 살펴보던 중 체스판과 말을 이용해서 새로운 게임을 만들기로 했다. 새로운 게임은 크기가 N×N인 체스판에서 진행되고, 사용하는 말의 개수는 K개이다. 말은 원판모양이고, 하�

www.acmicpc.net

 

색깔을 저장할 color 배열

체스판 위의 말들을 표시할 board

말들의 상태를 관리할 horse

 

 

보통 이런 문제를 풀 때는 board판 위에 움직여야 할 아이템들의 정보를 저장하고 board를 탐색하곤 했는데,

이번에는 보드판 따로, 아이템들의 정보 따로 저장했다.

그 이유는 0번부터 K-1번의 말이 움직일 때 그 이전의 말이 움직임이 그 다음 말에 영향을 주도록 해야 하기 때문이다.

예를 들어 낚시왕 같은 문제의 경우 이전 상어가 움직였을 때의 결과를 다른 곳에 따로 저장해야 했다. 한 사이클 동안 모든 상어가 동시에 움직인다고 했기 때문이다.

하지만 이번 문제는 말들이 순서대로 움직인다고 했기 때문에 board를 그대로 사용하고, 

한 턴마다 0~K-1번의 말들을 움직이기 위해서 horse 리스트에 따로 말들의 정보를 저장했다.

 

일단 주의해야 할 점은 파란색이거나 벽으로 막혀서 후진해야 될 때도 그 후진하려는 곳이 빨간색인지 흰색인지 구분하고 그대로 실행해줘야 한다는 것이다.

그리고 양쪽다 파란색이거나 벽으로 막혀서 움직이지 않을 때도 일단 방향은 바꿔주어야 한다.

그렇게 한번 움직임이 막힌 말은 더이상 움직이지 않을거라고 생각했는데,

말이 움직일 때 '그 위에 있는 말들'이 같이 움직이는 경우가 있으므로 왼오로 움직이던 말이 아래쪽 말에 영향을 받아서 상하로 움직일 수가 있다. 사면초가를 벗어날 수 있다는 뜻이다.

import sys


def solve():
    four = False
    for t in range(1001):
        for num_horse in range(K):  # 0~K-1번의 말을 순서대로 이동시키는 것이 한 '턴'
            r, c, direct = horse[num_horse]
            next_r = r + dx[direct]
            next_c = c + dy[direct]
            idx = board[r][c].index(num_horse)  # 체스판에서의 말의 인덱스

            if 0 <= next_r < N and 0 <= next_c < N:
                if color[next_r][next_c] == WHITE:  # h 위에 있는 모든 말이 이동하고 그 위에 쌓는다
                    board[next_r][next_c].extend(board[r][c][idx:])  # 이동하려는 말+그 위에 있는 말들을 이동시킨다
                    for _ in range(idx, len(board[r][c])):  # 말들의 정보 horse 리스트를 업데이트하고, 원래 있던 위치에서 삭제
                        horse[board[r][c][-1]] = [next_r, next_c, horse[board[r][c][-1]][DIRECT]]
                        del board[r][c][-1]
                    if len(board[next_r][next_c]) >= 4:
                        four = True
                        break
                elif color[next_r][next_c] == RED:  # h 위에 있는 말의 순서를 모두 바꾼후 같이 이동하고 그 위에 쌓는다
                    board[next_r][next_c].extend(reversed(board[r][c][idx:]))
                    for _ in range(idx, len(board[r][c])):
                        horse[board[r][c][-1]] = [next_r, next_c, horse[board[r][c][-1]][DIRECT]]
                        del board[r][c][-1]
                    if len(board[next_r][next_c]) >= 4:
                        four = True
                        break
                elif color[next_r][next_c] == BLUE:  # 방향을 반대로 바꾸고 한 칸 이동한다
                    if direct == 1 or direct == 3:
                        next_d = direct + 1
                    elif direct == 2 or direct == 4:
                        next_d = direct - 1
                    next_r, next_c = r + dx[next_d], c + dy[next_d]
                    if 0 <= next_r < N and 0 <= next_c < N:
                        if color[next_r][next_c] != BLUE:
                            if color[next_r][next_c] == WHITE:
                                board[next_r][next_c].extend(board[r][c][idx:])
                            elif color[next_r][next_c] == RED:
                                board[next_r][next_c].extend(reversed(board[r][c][idx:]))

                            for i in range(idx, len(board[r][c])):
                                horse[board[r][c][-1]] = [next_r, next_c, horse[board[r][c][-1]][DIRECT]]
                                del board[r][c][-1]
                            horse[num_horse] = [next_r, next_c, next_d]

                            if len(board[next_r][next_c]) >= 4:
                                four = True
                                break
                    horse[num_horse][DIRECT] = next_d  # 이동했건 안했던 일단 방향은 바뀐다

            else:  # 칸을 벗어나려는 경우 파랑과 같이 처리한다
                if direct == 1 or direct == 3:
                    next_d = direct + 1
                elif direct == 2 or direct == 4:
                    next_d = direct - 1
                next_r, next_c = r + dx[next_d], c + dy[next_d]
                if color[next_r][next_c] != BLUE:
                    if color[next_r][next_c] == WHITE:
                        board[next_r][next_c].extend(board[r][c][idx:])
                    elif color[next_r][next_c] == RED:
                        board[next_r][next_c].extend(reversed(board[r][c][idx:]))

                    for i in range(idx, len(board[r][c])):
                        horse[board[r][c][-1]] = [next_r, next_c, horse[board[r][c][-1]][DIRECT]]
                        del board[r][c][-1]

                    if len(board[next_r][next_c]) >= 4:
                        four = True
                        break
                horse[num_horse][DIRECT] = next_d  # 이동했건 안했던 일단 방향은 바뀐다

        if four:
            return t + 1  # t = 0부터 시작이므로 +1해서 리턴
    return -1


dx = (0, 0, 0, -1, 1)
dy = (0, 1, -1, 0, 0)
R, C, DIRECT = 0, 1, 2
WHITE, RED, BLUE = 0, 1, 2
input = sys.stdin.readline
N, K = map(int, input().split())  # N=체스판의 크기,K=말의 개수
color = [list(map(int, input().split())) for _ in range(N)]  # 색깔판
board = [[[] for _ in range(N)] for _ in range(N)]  # 체스판 위에 올라가 있는 말들이 저장될 리스트
horse = []  # 0~K-1번의 말들의 정보가 저장되는 리스트. '0'~'K-1'번을 말들의 번호라고 한다.
for k in range(K):
    i, j, d = map(int, input().split())  # row, col, 방향
    board[i-1][j-1] = [k]  # k=말의 번호. 여러 말이 저장될 수 있으므로 리스트로 저장한다
    horse.append([i-1, j-1, d])  # [row, col, 방향 d]
print(solve())

'알고리즘 > 시뮬레이션' 카테고리의 다른 글

3190번. 뱀  (0) 2020.05.04
17822번. 원판 돌리기  (0) 2020.05.02
17140번. 이차원 배열과 연산  (0) 2020.04.27
SWEA 2382번. 미생물 격리  (0) 2020.04.24
17144번. 미세먼지 안녕!  (0) 2020.04.21

https://programmers.co.kr/learn/courses/30/lessons/42895

 

 

dp[0] = N을 0번 사용해서 만들 수 있는 숫자들의 그룹

dp[1] = N을 1번 사용해서 만들 수 있는 숫자들의 그룹

dp[2] = N을 2번 사용해서 만들 수 있는 숫자들의 그룹

 

dp[3] = N을 3번 사용해서 만들 수 있는 숫자들의 그룹

        = [NNN] + dp[1]에 있는 모든 숫자들과 dp[2]에 있는 모든 숫자들을 +, -, *, / 해서 만들 수 있는 숫자들의 그룹

 

점화식:

dp(n) = [dp(i)의 모든 숫자들 + - * ÷ dp(n-i)의 모든 숫자들] (i = 1부터 n-1)

(점화식에서 55 555 5555 등의 숫자는 제외했다. 이러한 숫자들은 n마다 하나씩 있다. N을 n번 써준 숫자.)

 

문제에서 괄호까지 넣을 수 있다고 해서 헷갈렸다.

예를 들어 5를 3개 가지고 만들 수 있는 연산식 중 5 + 5 ÷ 5 가 있을 때,

(5 + 5) ÷ 5 와 5 + (5 ÷ 5)는 다른데 이걸 어떻게 고려해줘야 하나? 싶을 수도 있을 것이다.

하지만 다시 생각해보면 (5 + 5)와 (5 ÷ 5)는 5를 2개 가지고 만든 연산식이다. 

즉 저 식들을 일반화해서 생각해보면

[5를 2개 가지고 만들 수 있는 수] + - * ÷ [5를 1개 가지고 만들 수 있는 수]

[5를 1개 가지고 만들 수 있는 수] + - * ÷ [5를 2개 가지고 만들 수 있는 수]

가 되는 것이다. 그냥 두 번 쓴 게 아니다. -, ÷일 때는 식의 순서가 달라지면 결과도 달라질 수 있다.

 

j의 range를 (1, i // 2 + 1)로 잡은 이유는 

우선 dp[0]은 0이므로 볼 필요가 없다. dp[n] = dp[0] + - * ÷ dp[n]도 맞긴 한데, 어차피 다 중복되는 숫자들이라 해줄 필요가 없다.

range의 두번째 인자가 i // 2 + 1 라는 것은 j를 i // 2 까지 보겠다는 건데, 그 이유는 어차피 중복되기 때문이다.

예를 들어 dp(4) = dp(1) + - * ÷ dp(3), dp(2) + - * ÷ dp(2), dp(3) + - * ÷ dp(1) 인데 기울임 처리한 마지막 연산은 밑줄 친 첫번째 연산과 앞 뒤를 바꿔준 것 밖에는 다르지 않다. +와 *는 앞뒤가 바뀌어도 결과가 같기 때문에 신경 안 써도 되고, -와 ÷만 앞뒤로 바꿔서 해주면 된다.

 

i, j, k, m 등 요상한 변수가 많아서 헷갈릴 수 있는데 변수에 별다른 뜻이 없어서 어쩔 수가 없었다...

i = 2 부터 해서 직접 변수 안에 무엇이 들어가나 써보면 좀 이해하기 쉬울 것이다.

 

# Lvl3. N으로 표현


def solution(N, number):
    answer = 0
    dp = [0, {N}]  # N을 0번, 1번 가지고 만들 수 있는 수들의 집합
    
    if N == number:
    	return 1
        
    for i in range(2, 9):  # N을 2번 ~ 8번을 가지고 만들 수 있는 수들의 집합 구하기
        case = {int(str(N)*i)}  # N, NN, NNN ...
        for j in range(1, i // 2 + 1):  # dp(3) = [dp(1) +-*/ dp(2)] 여기서 j=1, i-j=2
            for k in dp[j]:
                for m in dp[i-j]:
                    case.add(k + m)
                    case.add(k * m)
                    
                    # j는 i의 절반까지만 구하고 연산할 때 앞뒤를 바꿔줌
                    case.add(k - m)
                    case.add(m - k)
                    if k > 0:
                        case.add(m // k)
                    if m > 0:
                        case.add(k // m)

        if number in case:
            #print(dp)
            return i
        else:
            dp.append(case)

    return -1

'알고리즘 > 동적프로그래밍' 카테고리의 다른 글

14501번. 퇴사 (DP)  (0) 2020.04.17
12865번. 평범한 배낭  (0) 2019.12.06
1912번. 연속합  (0) 2019.12.06
2565번. 전깃줄  (0) 2019.12.05
11054번. 가장 긴 바이토닉 부분 수열  (0) 2019.12.05

http://boj.kr/17822 

 

17822번: 원판 돌리기

반지름이 1, 2, ..., N인 원판이 크기가 작아지는 순으로 바닥에 놓여있고, 원판의 중심은 모두 같다. 원판의 반지름이 i이면, 그 원판을 i번째 원판이라고 한다. 각각의 원판에는 M개의 정수가 적혀있고, i번째 원판에 적힌 j번째 수의 위치는 (i, j)로 표현한다. 수의 위치는 다음을 만족한다. (i, 1)은 (i, 2), (i, M)과 인접하다. (i, M)은 (i, M-1), (i, 1)과 인접하다. (i, j)는 (i, j-1), (i, j

www.acmicpc.net

 

 

1. 원판을 2차원 배열로 생각해서 같은 원에 있는 숫자들은 같은 행에 속한 숫자들이라고 생각하고, 위아래로 접한 숫자들은 같은 열에 속한 숫자들이라고 생각한다.

행 (펼쳐준다)

2. 문제에서 주어진 일련의 과정을 몇가지로 나눈다.

 

1) 기저사례를 확인한다. 원판에 수가 남아있는지 확인해서 남아있으면 더 진행하고, 남아있지 않으면 반복문을 종료한다.

원판에 숫자가 남아있는지 추적하기 위해 N * M = remain이라는 변수를 따로 두었다. 

 

2) 각 행을 회전시킨다. 회전을 쉽게 하기 위해서 각 원판의 자료형을 list가 아닌 deque로 두었다. deque을 시계방향으로 회전하려면 deque.rotate(k), 반시계방향으로 회전하려면 deque.rotate(-k) 하면 된다. 너무 쉽다.

너무 쉬워서 찜찜하면 temp 배열을 하나 새로 만들어서 직접 위치를 계산해서 옮겨주면 된다.

 

3) 2차원 배열을 순환하면서 각 원소마다 인접한 숫자가 있는지 확인하고, 있으면 그 숫자를 지울 것이라는 표시를 담은 erase 배열에 True라고 체크한다.

문제를 처음 풀었을 때는 지문에서 각 행렬마다 어떻게 비교해야되는지 예외 케이스까지 조건을 친절하게 알려줬으므로 그대로 따라서 if-elif 문으로 비교했다. 물론 그래도 된다. 

 

하지만 어떤 문제에서는 이 문제처럼 직접 비교 조건을 알려주지 않고 그냥 인접한 숫자끼리 비교하라고만 할 수도 있다. 그럴 때는 지문에서 알려준 것처럼 i=0일 때, i=N-1일 때, 0 < i < N-1, j=0일 때, j=M-1일때, 0 < j < M-1 일때로 나눠서 코딩하기보다는 BFS나 DFS 탐색 문제처럼 동서남북 방향의 원소와 비교해야 한다고 생각하고 코딩하는 편이 빠를 것 같다. 중첩 if문으로 코딩하면 중복된 코드가 많아져서 자료형이라도 수정해야 될 때 긴 시간을 소모해야 할 수도 있다.

 

나는 미로찾기 문제를 BFS나 DFS로 풀 때처럼 dx와 dy를 미리 선언해주었다. 하지만 동서남북 방향의 네 원소와 모두 비교할 필요는 없고, →방향과 ↓방향의 원소와만 비교해도 된다. 어차피 중복되기 때문이다. 주의해야 할 점이 col은 원형 리스트이므로, col과 아래쪽 방향 next_c를 비교하려고 할 때 next_c가 범위를 벗어나면 0으로 바꿔줘야 한다. 반면 row는 원형 리스트가 아니므로 범위를 벗어나면 무시하면 된다.

 

4) 3번에서 지울 숫자들을 다 표시했으면, 이번에는 그 erase 배열을 순환하면서 True라고 체크된 숫자들을 0으로 지우고, 지웠으면 다시 erase 배열을 반복문에서 사용하기 위해서 False로 돌려놓는다. 5)번을 수행하기 위해서 지운 숫자가 있음을 표시하는 bool 변수 once_erase를 True로 체크한다.

 

5) 지운 숫자가 있음을 표시하는 bool 변수 once_erase가 False면 평균을 구해서 평균보다 작은 숫자는 +1, 큰 숫자는 -1을 해 주었다.

import sys
from collections import deque


def solve():
    remain = N * M
    for x, direct, k in move:  # x=x의 배수인 원판을 회전,direct=시계(0),반시계(1),k=몇칸 회전하는지
        if remain <= 0:  # 남아 있는 숫자가 없으면 반복문을 빠져나온 후 sum을 리턴
            break

        # x의 배수인 원판(행)을 모두 구해서 회전시킨다.
        if direct == 0:  # 시계방향
            for next_x in range(1, N+1):  # next_x = x의 배수인 원판(=행)
                if next_x % x == 0:
                    circle[next_x-1].rotate(k)
        else:  # 반시계방향
            for next_x in range(1, N+1):
                if next_x % x == 0:
                    circle[next_x-1].rotate(-k)

        # 인접한 것이 있는지 찾고, 있으면 지운다
        for row in range(N):
            for col in range(M):
                if circle[row][col]:  # 이미 지운 숫자는 보지 않는다
                    for i in range(2):  # 오른쪽, 아래쪽과 비교
                        next_r, next_c = row + dx[i], col + dy[i]
                        if next_c > M-1:  # col이 맨 끝에 도달했을 때는 next_c를 0으로 설정한다.
                            next_c = 0

                        if next_r < N:  # r은 범위를 벗어나면 그냥 무시한다
                            if circle[next_r][next_c] == circle[row][col]:
                                erase[next_r][next_c], erase[row][col] = True, True

        once_erased = False  # 지운 숫자가 있음을 체크할 bool 변수
        for row in range(N):
            for col in range(M):
                if erase[row][col]:  # 인접한 숫자가 있는 원소 (erase가 True로 체크된 원소)
                    once_erased = True
                    circle[row][col] = 0
                    remain -= 1  # 숫자를 지웠으니 남은 숫자를 의미하는 remain에서 1을 빼준다
                    erase[row][col] = False  # 다음 반복문에서 재사용하기 위해 다시 False로 돌려놓음

        # 지울 숫자가 없으면 평균을 구하고 평균보다 크면 -1, 작으면 +1
        if not once_erased:
            s = sum([sum(row) for row in circle]) / remain
            for row in range(N):
                for col in range(M):
                    if circle[row][col] > s:
                        circle[row][col] -= 1
                    elif 0 < circle[row][col] < s:
                        circle[row][col] += 1

    # 원판의 합을 리턴
    return sum([sum(row) for row in circle])


dx = (0, 1)
dy = (1, 0)
input = sys.stdin.readline
N, M, T = map(int, input().split())  # N=원판의 갯수,M=원판에 적혀있는 수의 갯수,T=회전수
circle = [deque(map(int, input().split()))for _ in range(N)]
erase = [[False for _ in range(M)] for _ in range(N)]  # 지울 숫자임을 표시하는 bool 배열
move = [list(map(int, input().split())) for _ in range(T)]
print(solve())

 

'알고리즘 > 시뮬레이션' 카테고리의 다른 글

3190번. 뱀  (0) 2020.05.04
17837번. 새로운 게임 2  (0) 2020.05.03
17140번. 이차원 배열과 연산  (0) 2020.04.27
SWEA 2382번. 미생물 격리  (0) 2020.04.24
17144번. 미세먼지 안녕!  (0) 2020.04.21
#include <stdio.h>
#include <stdlib.h>

struct Stack {
        struct Stack* next;
        int data;
};


int push(struct Stack* stack, int data) {
        struct Stack* newNode = malloc(sizeof(struct Stack));
        if (newNode == NULL) return -1;

        newNode->data = data;
        newNode->next = stack->next;
        stack->next = newNode;

        return 0;
}

int pop(struct Stack* stack, int* data) {
        struct Stack* popNode = stack->next; 
        if (popNode == NULL) return -1;  // pop할 노드가 NULL이면(스택에 데이터가 없음) 오류 리턴
 
        *data = popNode->data;  // pop할 데이터 저장
        stack->next = popNode->next;  // head가 가리키는 데이터를 pop할 노드가 가리키던 데이터로 이어줌
        free(popNode);  // head를 이어주었으므로 pop 노드는 삭제
        return 0;
}


/*
   element의 data와 같은 노드를 찾아서 삭제하는 함수.
   맨 앞에서부터 검색 후 삭제한다.
 */
int delete(struct Stack* stack, int element) {
        struct Stack* curPos = stack;
 
        while (curPos->next != NULL) {
                if (curPos->next->data == element) {  // 검색을 curPos의 next부터 
                        struct Stack* removeNode = curPos->next;
                        curPos->next = removeNode->next;
                        free(removeNode);
                        return 0;
                }
        }

        return -1;
}

/*
   스택을 생성하는 함수. 동적 배열을 사용하는 경우에 써야 하나,
   인터페이스의 일관성을 위해서 링크드리스트인 경우에도 추가함.
 */
int createStack(struct Stack* stack) {
        stack->next = NULL;
        return 0;
}

/*
   스택을 모두 지우는 함수
 */
int deleteStack(struct Stack* stack) {
        struct Stack* removeNode = stack->next;

        while (removeNode != NULL) {
                stack->next = removeNode->next;
                free(removeNode);
                removeNode = stack->next;
        }
}

/*
   beforeItem 다음에 item를 삽입하는 함수
 */
int insertAfter(struct Stack* stack, int beforeItem, int item) {
        struct Stack* curPos = stack->next;
 
        while (curPos) {
                if (curPos->data == beforeItem) {
                        struct Stack* newNode = malloc(sizeof(struct Stack));

                        newNode->next = curPos->next;
                        newNode->data = item;
                 
                        curPos->next = newNode;
                }
                curPos = curPos->next;
        }
        return -1;
}


/*
   모든 원소를 traverse하는 함수(디버깅용)
*/
int traverse(struct Stack* stack) {
        struct Stack* curPos = stack->next;

        while (curPos) {
                printf("%d ", curPos->data);
                curPos = curPos->next;
        }
        printf("\n");
        return 0;
}

void main() {
        printf("스택에 1, 2, 3을 넣은 후 3을 찾아서 삭제하고, pop을 실행합니다. 그리고 1 다음에 10을 삽입합니다..\n");
        struct Stack* stack = malloc(sizeof(struct Stack));

        createStack(stack);
        push(stack, 1);
        push(stack, 2);
        push(stack, 3);

        traverse(stack);
 
        struct Stack* deleteItem = malloc(sizeof(struct Stack));
        delete(stack, 3);
 
        traverse(stack);

        int data;
        pop(stack, &data);
 
        traverse(stack);

        insertAfter(stack, 1, 10);
 
        traverse(stack);
        deleteStack(stack);
        traverse(stack);
}

'알고리즘 > 자료구조' 카테고리의 다른 글

다리를 지나는 트럭 (Python)  (0) 2020.03.30

+ Recent posts