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

 

 

앞선 문제인 N과 M에서 사용되는 DFS를 활용한 문제이다.

중요한 아이디어는 같은 row에는 퀸이 하나밖에 존재할 수 없으며, 또 하나는 무조건 있다는 것이다.

이번 row에는 퀸이 어디에 와야할 지 column을 차례대로 늘려가면서 findColumn 함수를 통해 검사한다.

개인적으로는 row와 col이 서로 헷갈리고, for문의 range를 정하는 것 때문에 어려웠던 문제이다.

 

코드 설명은 주석으로 대신한다. 밑에서부터 보면 된다.

def isPossible(row, col):  # 위 혹은 양방향 대각선에 퀸이 있는지 검사
    # 좌우는 볼 필요가 없다. 하나의 row에는 하나의 퀸!

    # 위 (아래를 안 보는 이유는 아직 다음 row 자체를 안 봤기 때문
    for i in range(1, n):  # 주의: 1부터다. 0으로하면 7번째 줄에서 자기 자신과 같은지 검사하게 됨
        if row-i >= 0:  # row를 1씩 줄여가되 음수가 되지 않도록 한다. 
            if chess[row-i][col]:  # 퀸을 발견했다
                return False

    # \ 방향 대각선. row, col에서 1씩 빼면서 확인
    for i in range(1, n):
        if row-i >= 0 and col-i >= 0:
            if chess[row-i][col-i]:
                return False

    # / 방향 대각선. row는 -1, col은 +1 (그림으로 생각해보자)
    for i in range(1, n):
        if row-i >= 0 and col+i < n:
            if chess[row-i][col+i]:
                return False
    return True  # 위 모든 검사를 통과했다면 그거슨 퀸

def findColumn(row):
    global answer  # 전역변수를 함수 내에서 수정하기 위해 global 선언
    if row == n:  # 하나의 row에는 하나의 퀸이 무조건 들어가므로, row가 n이라면 퀸의 자리를 다 찾은 것임
        answer += 1
        return
    for col in range(n):  # row는 정해져 있으니 column을 1씩 증가시키면서 확인한다
        if isPossible(row, col):  # 놓을 수 있다면 
            chess[row][col] = True  # True로 퀸의 자리를 표시
            findColumn(row+1)  # 다음 row에서 퀸의 자리를 찾는다
        chess[row][col] = False  # 재귀가 끝난 후, 백트래킹해야하므로 다시 False로 돌려놓는다

n = int(input())
chess = [[False]*n for _ in range(n)]  # 체스판. True면 퀸이 있는 자리
answer = 0
findColumn(0)  # 인자는 row. 0번째 row부터 퀸이 들어갈 column을 찾는다.
print(answer)

 

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

2차원 배열에서 조합 선택하기  (0) 2019.12.20
14888. 연산자 끼워넣기  (0) 2019.11.28
2580. 스도쿠  (0) 2019.11.27
15650. N과 M (2)  (0) 2019.11.22
15649. N과 M(1)  (0) 2019.11.22

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

 

조합 문제이다.

구현에 있어서 순열과 다른 점은,

조합의 경우 순서가 없기 때문에 check만 확인해서 True인 것들만 출력하면 되지만

순열의 경우 1, 2, 5와 1, 5, 2가 다른데 check 확인 후 출력하면 항상 앞에서부터 1, 2 5로 출력이 될 것이기 때문이다.

따라서 조합은 뽑은 수열을 위한 리스트가 따로 없음을 유의하면 순열과 다른 점은 없다.

순열과 달리 시작부가 dfs(0, 0) = dfs(idx, cnt) 으로 시작하는 것은

dfs를 재귀호출 했을 때 리스트의 처음부터 다시 탐색해야 하는 '순열'과는 달리

조합은 지금까지 탐색했던 것들은 다시 탐색할 필요가 없기 때문에 현재 인덱스를 저장해놓기 위함이다.

(설명추가: 조합은 1을 한번 선택해서 조합을 꾸렸다면, 그게 1이 포함된 조합의 전부이다.

즉, 1이 포함된 조합 선택이 끝나면, 그 이후로는 1을 선택하지 않을 것이다. )

n, m = map(int, input().split())
lst = []
check = [False] * n
for i in range(1, n+1):
    lst.append(i)

def Print():
    for i, v in enumerate(check):
        if v == True:
            print(lst[i], end = ' ')
    print("")

def dfs(idx, cnt):
    if cnt == m:
        Print()
        return
    for i in range(idx, n):
        if check[i] == True:
            continue
        check[i] = True
        dfs(i, cnt+1)
        check[i] = False

dfs(0, 0)  # lst의 0번째 인덱스부터 살펴보면서 / 0번째 수열부터 뽑을 것임

 

 

https://yabmoons.tistory.com/99

를 참고했다.

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

2차원 배열에서 조합 선택하기  (0) 2019.12.20
14888. 연산자 끼워넣기  (0) 2019.11.28
2580. 스도쿠  (0) 2019.11.27
9663. N-Queen  (0) 2019.11.26
15649. N과 M(1)  (0) 2019.11.22

백트래킹 단계를 시작하면서, 이게 무엇인지부터 찾아봤다.

완전 탐색의 일종인데, 완전 탐색은 모든 경우를 다 살펴보지만

백트래킹의 경우 그게 아니라면 쳐내고 살펴본다고 한다.

DFS, BFS가 그 예시라고 함.

 

나같은 경우 직접 순열을 DFS로 코딩해본 적이 없어서 기본기를 잡느라 오래 걸렸던 문제였다.

DFS가 무엇인지 알고, 스택을 사용한다는 것도 알고 있었는데 직접 내 손으로 만들어보려니 생각이 나질 않았다.

많이 쓰이는 알고리즘이라고 하니 한 번 이해하고 스택 구현하듯이 외울 정도가 되어도 나쁘지 않을 것 같다.

 

n, m = map(int, input().split())
lst = []
for i in range(1, n+1):
    lst.append(i)
check = [False] * n
tmp = []

def Print(string):
    for num in string:
        print(num, end=' ')
    print("")

def dfs(cnt):
    if cnt == m:
        Print(tmp)
        return
    for i, v in enumerate(check):
        if v == True:
            continue
        check[i] = True
        tmp.append(lst[i])
        dfs(cnt+1)
        tmp.pop()
        check[i] = False

dfs(0)

 

자세한 설명은 밑에 참고한 블로그에 써 있긴 하지만, 정리할 겸 내 언어로 풀어가보면 이렇다.

코드를 실행하면 마지막 줄의 dfs(0)부터 시작되는데 맨 처음에 인덱스 0부터 탐색하겠다는 뜻이다.

15649번 문제를 보면 '오름차순'으로 정렬하라고 했기 때문이다. 리스트를 오름차순으로 정렬해놨기 때문에...

 

dfs 함수에 처음 들어가게 되면, 14번줄 cnt가 m인지 묻는 것은 나중을 위한 것이다. 

일단 17번째 줄을 보게 되면 check 리스트를 순회하는 것을 볼 수 있다.

check 리스트는 False로 초기화되어있는데,

처음에는 True인 것이 아무것도 없으므로 일단 check[0]을 True로 만들고 (=순열로 선택하고)

같은 인덱스를 가진 리스트의 원소를 tmp 리스트에 추가시킨다. (21 line)

중요한 것은 22번째 줄 dfs 함수를 재귀호출하는 것에 있는 듯 하다.

아까 말했듯 14번째 줄에 m개만큼 선택을 전부 다 했는지 묻고, 그렇다면 프린트를 한다.

이후에 재귀를 벗어나 15번째 줄로 돌아오게 되는데, 선택했던 것을 다시 빼내고 check도 False로 바꾼다.

이러한 아이디어다.

 

중간에 실수했던 점은 tmp.pop()을 재귀함수 밑이 아닌 프린트 함수 내에 썼던 것인데,

프린트를 한다=출력을 한번 했으니까 그 다음엔 빼내야지. 라고 단순하게 생각했기 때문이다.

저 생각이 틀린 것은 아닌데, 문제는 pop의 위치이다.

프린트할 때만 pop을 하니까 

1 2

1 3 

1 4

1 5 

1 2 3

1 2 4

...

이런 식으로 출력이 되었다. 1, 5 -> 1, 2, 3으로 넘어갈 때 5만 pop하고 그 다음 넣을 원소를 찾게 됐던 것.

14번째 줄의 cnt == m 일 때만 pop을 했던 것이다.

1, 5에서 5를 빼낸 후 리스트에 1만 남아있을 때 다시 재귀 복귀를 해서 또 pop이 이루어져서 1도 빼내야 하는데 말이다.

 

말로 하니까 어려운데...그랬다.

 

 

 

https://yabmoons.tistory.com/99

 

[ 순열과 조합 구현 ] - 재귀를 통한 구현(1 - 조합) (C++)

브루트포스 알고리즘에서 가장 많이 사용되는 방법이 순열과 조합등으로 모든 경우의 수를 모두 계산해본 뒤에 원하는 결과 값을 찾는 방식이다. 이 글에서는, 순열과 조합을 STL을 사용하지 않고, 재귀를 통해서..

yabmoons.tistory.com

를 참고했다.

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

2차원 배열에서 조합 선택하기  (0) 2019.12.20
14888. 연산자 끼워넣기  (0) 2019.11.28
2580. 스도쿠  (0) 2019.11.27
9663. N-Queen  (0) 2019.11.26
15650. N과 M (2)  (0) 2019.11.22

동적 프로그래밍 하다가 이건 아닌 것 같아서 완전탐색부터 시작.

범위가 1~1000000이었기 때문에 완전탐색이라는 것은 쉽게 알아낼 수 있을 것 같다.

하지만 완전탐색 중에서도 범위를 줄여낼 수 있었던 문제.

 

enter = int(input())
answer = 0
for i in range(enter-54, enter):
    if i <= 0:
        continue
    tmp = i
    ins = i  # 분해합
    while tmp > 0:
        if tmp < 1:
            ins += tmp
            break
        else:
            ins += tmp % 10
            tmp = tmp // 10
    if ins == enter:
        answer = i
        break

print(answer)

 

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

16637. 괄호 추가하기 (2)  (0) 2020.05.12
16637번. 괄호 추가하기  (0) 2020.05.11
12100번. 2048 (Easy)  (0) 2020.05.04
17609번. 회문  (0) 2020.04.19
12100. 2048 (Easy)  (0) 2020.01.07

어렵다.

동적 프로그래밍은 점화식을 세우면 풀 수 있다고 했는데,

점화식은 세웠는데 그걸로 어떻게 구현해야할지 재귀에서 한참 헤맸다.

처음이라 그런지, 피보나치만 생각하고 Top-down만 생각했던 게 실수였던 것 같다.

또, 점화식이 D(n) = min(D(n/3), D(n/2), D(n-1)) + 1 이런 식으로 나오다보니 자연스럽게 탑다운만 생각했다.

 

사실, 탑다운도 가능하고 바텀업도 가능하다.

하지만 실행 시 탑다운 방식은 큰 수를 넣으면 재귀가 너무 깊다고 하면서 오류가 뜬다.

 

 

 

Top-Down (큰 수 오류)

enter = int(input())
m = [-1 for i in range(enter + 1)]  # memoization

def dp(n):
    if n == 1:
        return 0
    if n == 2 or n == 3:
        return 1
    if m[n] != -1:
        return m[n]
    temp1 = dp(n-1) + 1  # -1 했을 때 최소 연산
    temp2 = temp1  # 0으로 하면 안되서 temp1과 같게 해 줌
    temp3 = temp1
    if n % 2 == 0:
        temp2 = dp(n//2) + 1
    if n % 3 == 0:ㅁ
        temp3 = dp(n//3) + 1
    m[n] = min([temp1, temp2, temp3])
    return m[n]

print(dp(enter))

 

 

 

Bottom-Up

n = int(input())
m = []  # 미리 초기화를 하면 밑부분 m[0]~m[3]을 초기화해주는 부분에서 out of index 날 수 있다

m.append(0)
m.append(0)
m.append(1)
m.append(1)

for i in range(4, n+1):
    m.append(m[i-1] + 1);
    if(i % 2 == 0):
        m[i] = min(m[i], m[i//2] + 1) 
    if(i % 3 == 0):
        m[i] = min(m[i], m[i//3] + 1) 

print(m[n])

 

이 문제에서 생각해내지 못한 것은 10번째 줄이다.

-1 하는 것을 기본으로 놓고,

그 이후에 2나 3으로도 계산해보면서 무엇이 최솟값인지 비교해보는 방식으로 푸는 것이었다.

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

1463. 1로 나누기 (열흘 전과 코드 비교)  (0) 2019.12.03
2579. 계단 오르기  (0) 2019.12.03
1932번. 정수 삼각형  (0) 2019.12.03
1149. RGB거리  (0) 2019.12.03
막대기 자르기 문제  (0) 2019.11.19
0 1 2 3 4 5 6 7 8 9
0 1 5 8 9 10 17 20 24 30

첫째 줄은 막대기의 길이이다.

둘째 줄은 각 막대기의 가격이다.

한 막대기를 선택해서, 그 막대기를 적절하게 자르든 안 하든, 가장 비싼 가격을 만들어야 한다.

예를 들어 길이 4의 막대기는 2로 나누어서 5+5=10이 최대 가격이 된다.

 

막대기 n을 선택해 나누려고 하는데,

i를 1부터 시작해서 i : n-i 비율로 나눴다고 생각하자. 

예를 들어 막대기 4를 1:3, 2:2, 3:1, 4:0 비율로 나눌 수 있다. 

이 때 i는 1부터 n까지 모든 경우를 볼 것이기 때문에 더이상 건들지 말고 표 가격 그대로라고 생각하고,

n-i의 가격을 최대 가격이라고 생각하면

i=n까지 계산 및 비교를 끝냈을 때 n의 최대 가격을 알아낼 수 있을 것이다.

4를 1:3으로 나눴다면 1을 기준점으로 삼고 그냥 두고, 3을 막대기 3의 최대 길이라고 생각하자는 것이다.

이것을 점화식으로 나타내면 다음과 같다.

 

Rn = max(Pi + Rn-i) (i=1~n. R은 최대 가격, P는 표에 주어진 가격)

 

R1 = max(P1 + R0)

R2 = max(P1 + R1, P2 + R0)

R3 = max(P1 + R2, P2 + R1, P3 + R0)

R4 = max(P1 + R3, P2 + R2, P3 + R1, P4 + R0)

....

계속해서 R0, R1, R2 등의 계산이 반복되고 있기 때문에 동적 프로그래밍으로 풀어갈 수 있다.

다이나믹 프로그래밍은 점화식을 알아내면 풀 수 있다고 한다. 

저 문제를 봤을 때 DP인 것을 인식하고 수식을 생각해낼 수 있을지 솔직히 지금은 자신없지만...

 

 

 

 

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

1463. 1로 나누기 (열흘 전과 코드 비교)  (0) 2019.12.03
2579. 계단 오르기  (0) 2019.12.03
1932번. 정수 삼각형  (0) 2019.12.03
1149. RGB거리  (0) 2019.12.03
1463. 1로 만들기  (0) 2019.11.19

동전 문제는 그리디 알고리즘이 되는 이유가

예를 들어 화폐가 500원, 100원 두 가지라면 500과 100의 최소공배수가 더 작은 수(100)이기 때문이라고 한다.

100x5=500으로 표현 가능하지만 이러할 경우 500으로 표현하는 것이 유리하기 때문에,

내림차순으로 정렬한 후에 그리디 알고리즘으로 해결 가능하다는 것 같다.

만약 화폐가 500원, 400원이라면 500은 400의 자연수 배수로 표현할 수 없기 때문에 그리디 알고리즘으로 풀 수 없다고 한다.

 

answer = 0

price = int(input())
change = 1000 - price
bank = [500, 100, 50, 10, 5, 1]

for coin in bank:
    if change >= coin:
        answer += change // coin
        change %= coin
        if change == 0:
            break

print(answer)

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

1931. 회의실 배정  (0) 2019.11.18

분명 문제가 그리디 카테고리에 있었지만 처음엔 완전탐색으로 풀었던 것 같다.

0부터 시작해 가능한 경우를 다 세보면서, 최대 회의 수를 max로 두고 마지막 max를 출력하는 방식으로 풀었다.

그렇게 하니 예시에 대한 답은 맞췄지만 채점 중 시간초과가 나왔다.

 

결국 구글링으로 힌트를 찾았다.

포인트는 회의가 끝나는 시간을 기준으로 정렬한 후에 그리디로 푸는 것.

회의가 끝나는 시간을 기준으로 삼으면, 앞으로 회의를 진행할 수 있는 남은 시간이 많이 남기 때문이라는 것이다.

 

만약 이 문제 유형이 그리디라는 것을 몰랐다면, 그리디로 풀어낼 수 있었을까?

 

생각하지 못했을 것 같다.

아직 처음이니까, 풀다보면 감이 오겠지.

 

answer = 0

n = int(input())
timeTable = []
for i in range(n):
    timeTable.append(tuple(map(int, input().split())))
timeTable.sort(key=lambda t: (t[1], t[0]))

last = 0
for t in timeTable:
    if t[0] >= last:
        answer += 1
        last = t[1]

print(answer)
 

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

5585. 거스름돈  (0) 2019.11.18

+ Recent posts