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

 

 

 

문제를 대충 읽고 삼성st Flood fill 문제구만! 하고 시작했더니 보기좋게 틀려버렸다.

이 문제는 어떤 그래프에서 서로 연결된 클러스터들이 몇 개인지 찾아내는 문제이다.

 

2667번. 단지 번호 붙이기 문제와는 확실히 다른 문제이다. 단지 번호 붙이기 문제는 인접 행렬이 주어지는 게 아니라 단순히 2차원 행렬이 주어지는 것이었다.

하지만 이 문제는 인접 행렬이 주어지는 문제이다. 단지번호붙이기 문제에서는 (0, 0)에서 곧바로 (0, 4)를 방문하지 못하지만 인접 행렬 문제에서는 방문할 수 있다.

인접행렬에서 (0, 4) = 1 이라는건 이런 걸 의미한다.

차이를 말하자면 2차원 배열과 1차원 배열의 차이라고 할까? 사실 이 두 문제는 너무나도 다른데 내가 헷갈린거라 설명할 말도 없다. 

단지번호붙이기에서 주어진 행렬은 각 노드의 위치를 알려준 거지만 (row, col)

이 문제에서의 행렬은 인접행렬이므로 두 노드가 연결되어있는지를 알려준 것이다. (v, next_v)

그래서 visited 배열은 1차원 배열이 되어야 한다. (각 노드에 row, col 위치가 없고 노드 이름만 존재하므로)

 

+ 인접행렬에서 자기 자신으로 향하는 것을 1로 표시한다고 했는데, 이 말은 그래프 탐색을 할 때 자기 자신은 아무 처리하지 않고 일단 방문한 뒤 쓰루한다는 것을 의미한다. 

# 네트워크
def dfs(v, adj, visited):
    for next_v in range(len(adj)):
        if not visited[next_v] and adj[v][next_v]:
            visited[next_v] = 1
            dfs(next_v, adj, visited)


def solution(n, computers):
    answer = 0
    visited = [0 for _ in range(n)]

    for v in range(n):
        if not visited[v]:
            dfs(v, computers, visited)
            answer += 1

    return answer

'알고리즘 > BFS와 DFS' 카테고리의 다른 글

1167번. 트리의 지름  (0) 2020.05.23
11725번. 트리의 부모 찾기 (Python)  (0) 2020.05.23
17142번. 연구소 3  (0) 2020.04.26
15971번. 두 로봇  (0) 2020.04.21
14501번. 퇴사  (0) 2020.04.17

http://boj.kr/16637 

 

16637번: 괄호 추가하기

첫째 줄에 수식의 길이 N(1 ≤ N ≤ 19)가 주어진다. 둘째 줄에는 수식이 주어진다. 수식에 포함된 정수는 모두 0보다 크거나 같고, 9보다 작거나 같다. 문자열은 정수로 시작하고, 연산자와 정수가

www.acmicpc.net

 

 

import sys


def _calc(n1, n2, oper):
    if oper == '+':
        return n1 + n2
    elif oper == '-':
        return n1 - n2
    elif oper == '*':
        return n1 * n2


def calc():
    global max_result
    buf = []  # 연산식을 담는데, 괄호는 미리 계산해서 담는다
    
    i = 0
    while i < len(arr):
        if i+2 < len(arr) and paren[i] == paren[i+2] == True:
            tmp = _calc(int(arr[i]), int(arr[i+2]), arr[i+1])  # 괄호 안의 식은 미리 계산
            buf.append(tmp)
            i += 3  
        else:
            if arr[i].isdigit():
                buf.append(int(arr[i]))
            else:
                buf.append(arr[i])
            i += 1

    i = 2
    ret = buf[0]
    while i < len(buf):
        ret = _calc(ret, buf[i], buf[i-1])
        i += 2
    max_result = max(max_result, ret)


def dfs(i):
    while i < len(arr) - 2:  # 마지막 괄호를 쳐볼 수 있는 숫자는 끝에서 두 번째 숫자이다
        if paren[i] == False:
            paren[i] = paren[i+2] = True  # 이번 숫자와, 다음 숫자에 괄호를 표시
            dfs(i+2)
            paren[i] = paren[i+2] = False  # 재귀 함수에서 돌아온 이후에는 괄호를 체크 해제
        i += 2
    calc()
    return


input = sys.stdin.readline
N = int(input())
arr = list(input()[:-1])  # 숫자와 연산자를 모두 한 배열에 담는다.
paren = [False for _ in range(len(arr))]  # 괄호쳤음을 표시
max_result = float('-inf')
dfs(0)
print(max_result)

 

 

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

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

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

크루스칼 알고리즘은 최소 신장 트리(MST: Minimum Spanning Tree)를 찾는 알고리즘이다.

프림 알고리즘과 같이 그리디 알고리즘에 속한다.

간선을 가중치 순으로 정렬하고 가중치가 짧은 순서대로 그래프에 포함시키기 때문이다.

 

프림 알고리즘은 비어 있는 트리에 정점을 하나씩 추가시키면서 조금씩 트리의 크기를 키워나가는 방식이었다.

반면 크루스칼 알고리즘은 애초에 모든 정점을 크기가 1인 트리라고 가정한다.

이러한 트리 사이의 간선을 가중치가 작은 순서대로 정렬한 후, 조각조각 간선을 이어 붙여서 최종적으로 하나의 트리로 만들어낸다.

이때 주의점은 정점을 이어붙이는 프림 알고리즘과는 달리 간선을 이어붙이기 때문에 가중치가 작은 순서대로 무조건 간선을 이어붙이다간 사이클이 생길 수가 있다는 것이다.

사이클이 생기면 더이상 트리가 아니기 때문에, 사이클이 생기도록 해선 안된다.

그렇다면 사이클이 생기는 지는 어떻게 알 수 있을까? 직접 노드의 부모를 타고 올라가서 루트가 같은지 확인해야 할까?

처음에는 그렇게 생각했지만, 트리마다 루트를 정해준 후에 두 트리를 합칠 때 루트를 비교하는 방법이 있다는 것을 알게 되었다.

예를 들어보자. A와 B가 결혼하려고 하는데 결혼하기 전에 혹시 A와 B의 집안이 같은 집안인지 확인하고 싶다. 

그럴때 A에게 부모를 물어보고, A의 부모에게 부모를 물어보고, A의 부모의 부모에게 부모를 물어보고...계속 이것을 반복해야 할까? 너무 오래 걸리는 방법이다.

한번에 알아낼 수 있는 방법이 있다. A에게 그의 시조가 누구인지 물어보면 된다.

A의 시조는 ㅇㅇㅇ이고 B의 시조는 ㅁㅁㅁ이니까 두 집안은 다른 집안이야! 이렇게 시조를 저장해놓으면 두 집안이 같은지 O(1)만에 알아낼 수 있을 것이다.

 

여기 코드에서 시조, 즉 루트를 알아내는 함수는 find_set이다. find-set은 집합 처리 부분에서 나오는 그것인데 집합의 루트를 반환한다고 생각하면 된다. 

 

그리고 두 집안이 다르면 결혼을 성사시키는데, 미국의 경우 대부분 여자가 남자의 성을 따라간다.

트리에도 이것을 적용해볼 수 있다. 남자 A의 시조가 스미스 씨였고 여자 B의 시조는 베이커 씨였는데, A와 B가 결혼함으로써 B의 시조가 스미스 씨가 되는 것이다! (실제로는 아니겠지만 크루스칼 알고리즘 사회에서는 그렇다고 치자)

즉, 노드 B의 루트가 A의 루트를 가리키게 하면 된다.

실제로 크루스칼 알고리즘에서 누가 누구를 가리킬 것이냐에 대해서는 그저 한 가지 기준을 정하면 되지만 여기서는 노드의 숫자가 큰 것이 → 작은 것을 가리키게 하는 방법을 썼다.

 

여기 코드에서 두 트리를 병합하는 함수는 union_set이다.

 

갑자기 생각난건데, 전 세계 인구를 족보 순으로 가장 가까운 순으로 연결시켜서 하나의 족보로 만든다면 그것이 크루스칼 알고리즘이 아닐까?

 

# 크루스칼 알고리즘


# 모든 간선의 정보를 저장할 클래스. a는 한 쪽 노드, b는 다른 한 쪽 노드이며 dist는 a와 b 사이의 가중치(거리)이다.
class Edge:
    def __init__(self, a, b, dist):
        self.a = a
        self.b = b
        self.dist = dist


# 루트 노드를 반환하는 함수.
# 루트 노드를 구할 때 그냥 root[x]해도 되겠지만 집합 처리 개념을 사용하기 위해 함수로 따로 빼냈다.
def find_set(x):
    return root[x]


# 두 트리를 병합하는 함수. 일관성을 위해 노드 번호가 더 작은 것을 루트로 만든다.
def union_set(a, b):
    a = find_set(a)
    b = find_set(b)
    if a < b:
        root[b] = a
    else:
        root[a] = b


# edges에는 [정점 a, 정점 b, a-b 사이의 거리]를 담은 Edge 객체들이 저장되어 있다.
edges = list()
edges.append(Edge(1, 7, 12))
edges.append(Edge(1, 4, 28))
edges.append(Edge(1, 2, 67))
edges.append(Edge(1, 5, 17))
edges.append(Edge(2, 4, 24))
edges.append(Edge(2, 5, 62))
edges.append(Edge(3, 5, 20))
edges.append(Edge(3, 6, 37))
edges.append(Edge(4, 7, 13))
edges.append(Edge(5, 6, 45))
edges.append(Edge(5, 7, 73))

n = len(edges)
root = [i for i in range(n)]  
# 모든 트리의 루트를 저장. 맨 처음에는 노드가 하나의 트리이므로 자기 자신이 루트이다.

total = 0  # 최단 거리

edges.sort(key=lambda x: x.dist)  # 우선 간선을 가중치 순으로 정렬. 가중치가 작은 순으로 순회

for edge in edges:  # 모든 간선에 대해서 반복한다.
    if find_set(edge.a) != find_set(edge.b):
    # 두 노드가 포함된 트리의 루트가 서로 다를 때(=두 트리를 합쳐도 사이클이 생기지 않을 때)
        union_set(edge.a, edge.b)  # 두 노드(가 포함되어 있는 트리)를 병합
        total += edge.dist
print(total)

'CS > 알고리즘' 카테고리의 다른 글

Merge Sort in Python (병합 정렬)  (0) 2020.06.09
Insertion Sort in Python (삽입 정렬)  (0) 2020.06.09
프림 알고리즘 (Python)  (0) 2020.05.07
Call by Value vs Call by Reference  (0) 2020.04.28
Selection Sort (선택 정렬)  (0) 2020.04.24

신장 트리는 그래프 G=(V, E)에서 간선을 V의 갯수-1개만 남겨서 트리로 만든 것을 말한다. (V=vertex, E=edge)

최소 신장 트리(MST: Minimum Spanning Tree)는 간선들이 가중치가 있는 그래프에서 간선 가중치의 합이 가장 작은 트리를 말한다.

프림 알고리즘은 이러한 최소 신장 트리를 찾는 방법 중 하나로, 그리디 알고리즘에 속한다.

 

 

# 프림 알고리즘

INF = float('inf')

# 각 정점 사이의 가중치가 주어진다.
weight = [[0, 7, INF, INF, 3, 10, INF],
          [7, 0, 4, 10, 2, 6, INF],
          [INF, 4, 0, 2, INF, INF, INF],
          [INF, 10, 2, 0, INF, 9, 4],
          [3, 2, INF, INF, 0, INF, 5],
          [10, 6, INF, 9, INF, 0, INF],
          [INF, INF, INF, 4, 5, INF, 0]]

# 집합 S: 최종적으로 만들어질 트리. 처음에는 아무것도 포함되지 않았다고 가정한다.
# 프림 알고리즘에서는 모든 정점에 대해 S와의 거리를 저장한 dist라는 배열을 두고, 이 중 가장 가까운 정점을 S에 하나씩 포함시킨다.
# 새로 포함된 정점 때문에 그 정점에 인접한 점들은 S에 포함될 수 있는 최단거리가 갱신될 수 있으므로 확인 후 갱신한다.

V_NUM = len(weight[0])
dist = [INF for _ in range(V_NUM)]  # 모든 정점에 대해서 집합 S와의 최단거리. 처음에는 모두 무한대라고 가정한다.
selected = [False for _ in range(V_NUM)]
dist[0] = 0  # 시작 정점을 선택하고 S에 포함하고, 거리가 0이라고 가정한다. (프림 알고리즘의 시작)

for _ in range(V_NUM):  # 정점의 갯수만큼 반복

    unselected = [idx for idx, val in enumerate(selected) if not val]
    u = min(unselected, key=lambda x: dist[x])
    # u=아직 집합 S에 포함되지 않은 정점 중에서 집합에 연결되기 위해 최소 비용이 드는 점을 구한다.

    selected[u] = True
    print(u)  # S에 포함된 점

    for v in range(V_NUM):
        if weight[u][v] != INF:  # u와 연결된 정점 중에서
            if not selected[v] and weight[u][v] < dist[v]:
                # S에 포함되지 않은 정점 중에서,
                # 이미 알려진 길(dist[v])보다 더 가까운 길로 갈 수 있으면(weight[u][v]) 갱신한다. 다음에 방문하기 위해서..
                dist[v] = weight[u][v]

https://mattlee.tistory.com/46 를 참고하였음

 

 

'CS > 알고리즘' 카테고리의 다른 글

Insertion Sort in Python (삽입 정렬)  (0) 2020.06.09
크루스칼 알고리즘 (Python)  (0) 2020.05.07
Call by Value vs Call by Reference  (0) 2020.04.28
Selection Sort (선택 정렬)  (0) 2020.04.24
Bubble Sort (버블 정렬)  (0) 2020.04.24

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

+ Recent posts