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

 

2613번: 숫자구슬

첫째 줄에 구슬의 개수 N과 그룹의 수 M이 주어진다. 둘째 줄에는 각 구슬이 적혀진 숫자가 왼쪽부터 차례로 주어진다. N은 300 이하의 자연수, M은 N이하의 자연수이며, 구슬에 적혀진 숫자는 100

www.acmicpc.net

 

  • 왼쪽부터 차례대로 숫자구슬을 묶으면서 묶여있는 구슬의 합의 최댓값이 최소가 되게 하는 문제.
  • 처음엔 간단하게 조합을 이용하여 풀어보려고 했지만, 골드2 문제답게 조합으로 풀면 시간 초과가 나게 된다.
  • 이분탐색과 그리디 알고리즘을 이용하여 시간을 낮추어 풀 수 있었다.
  1. 숫자구슬 묶음의 개수는 최소 1개 최대 N개가 된다
  2. 숫자구슬 묶음을 1개로 두었을 때 최대값은 리스트의 전체 합 즉, sum(n_lst)가 될 것이고, 숫자구슬 묶음을 N개로 두었을 경우 최대값은 리스트 요소 중 최대값 max(n_lst)가 될 것이다
  3. 결국 숫자구슬 묶음의 최댓값은 max(n_lst) <= X sum(n_lst)가 될 것이기 때문에 해당 값이 이분탐색으로 구한뒤 최댓값으로 가능한지 그리디 알고리즘을 이용하면 풀면 되는 문제였다.

 

** 이분탐색을 사용하기 위해 start, end값 설정해주기 **

 

N, M = map(int, input().split())

n_lst = list(map(int, input().split()))

# 이분탐색을 하기 위해 start와 end값을 찾아야 함
# start값은 1개를 담았을 경우의 최댓값
# end값은 한번에 모든 것을 담았을 경우의 최댓값
# M개의 묶음을 만들어 값을 만들어낼 수 있는 경우를 찾았더라도 최댓값 중 최솟값을 찾아야 하므로 더 낮춰야 함 (end값 낮추기)
start = max(n_lst)
end = sum(n_lst)
min_ans = int(1e9)

 

 

** 이분탐색을 돌리면서 최댓값이 가능한지 체크해주고, 그리디 알고리즘을 이용하여 M개의 묶음이 가능한지를 체크 **

 

  • 1, 2, 3, 4, 5 이 구슬들 중 현재 최댓값이 가능한지 체크하는 숫자를10이라고 정해두었다고 생각해보자
  • (1, 2, 3, 4) (5) 이렇게 묶음을 나눌 수 있는데, 이 때 만약 내가 구슬을 3개의 묶음으로 만들고자했다면 4개의 구슬을 분할해주면 된다. 그렇기 때문에 내가 묶고 싶은 묶음의 개수 M개보다 작은 경우에도 M개의 묶음이 가능하게 된다.
while start <= end:
    mid = (start + end) // 2
    cnt = 0 #묶음의 개수
    sum = 0 #현재 묶음의 값
    sum_lst = []
    cnt_lst = []
    for i in range(len(n_lst)):
        check = sum + n_lst[i] #현재 묶음에 구슬을 추가로 넣을 수 있는지 체크
        if check > mid: #만약 현재 묶음에 구슬을 추가로 넣을 수 없다면
            cnt_lst.append(cnt) #현재 구슬의 개수와 값을 넣어두고
            sum_lst.append(sum)
            sum = 0 #묶음 구슬의 개수와 값을 초기화시켜준다
            cnt = 0
        # 그런 뒤 다시 값과 개수를 늘려주면 그리디 알고리즘 완료        
        sum += n_lst[i]
        cnt += 1
    #반복문의 마지막은 값과 개수를 늘려주는 것으로 끝나므로 마지막에 추가해준 값과 개수까지 append로 따로 넣어준다.
    sum_lst.append(sum)
    cnt_lst.append(cnt)

 

 

** 그리디 알고리즘이 끝났으면 해당 묶음의 개수를 체크하고, 원하는 M개보다 작거나 같으면 개수와 값을 갱신해준다. **

 

    if len(sum_lst) <= M: # 만들어진 개수가 M보다 작거나 같다면 해당 값으로 묶음을 만들 수 있다

        end = mid - 1 # 좀 더 작은 최댓값으로도 묶음을 만들 수 있는지 체크하기 위해 end = mid - 1

        if max(sum_lst) < min_ans:
            min_ans = max(sum_lst) 
            min_cnt = cnt_lst
    else:
        start = mid + 1
print(min_ans)

 

 

** 이제 추가로 작업을 해주어야 할 것은 묶음이 M개보다 작을 경우의 체크이다 **

 

while len(min_cnt) != M: #묶음의 개수가 같아질 때 까지
   if min_cnt[i] == 1 : # 묶음 안의 구슬 개수가 1개일 경우 
       i += 1 #다음 구슬 체크
   else : #묶음 안의 구슬 개수가 1개를 초과할 경우
       min_cnt[i] -= 1 #해당 묶음의 구슬 개수를 하나 줄이고
       min_cnt.insert(i+1, 1) #그 묶음의 바로 앞에 추가로 구슬 묶음을 만들어준다.
print(*min_cnt)

 

** 최종코드 **

 

N, M = map(int, input().split())

n_lst = list(map(int, input().split()))

# 이분탐색을 하기 위해 start와 end값을 찾아야 함
# start값은 1개를 담았을 경우의 최댓값
# end값은 한번에 모든 것을 담았을 경우의 최댓값
# M개의 묶음을 만들어 값을 만들어낼 수 있는 경우를 찾았더라도 최댓값 중 최솟값을 찾아야 하므로 더 낮춰야 함 (end값 낮추기)
start = max(n_lst)
end = sum(n_lst)
min_ans = int(1e9)

while start <= end:
    mid = (start + end) // 2
    cnt = 0 #묶음의 개수
    sum = 0 #현재 묶음의 값
    sum_lst = []
    cnt_lst = []
    for i in range(len(n_lst)):
        check = sum + n_lst[i] #현재 묶음에 구슬을 추가로 넣을 수 있는지 체크
        if check > mid: #만약 현재 묶음에 구슬을 추가로 넣을 수 없다면
            cnt_lst.append(cnt) #현재 구슬의 개수와 값을 넣어두고
            sum_lst.append(sum)
            sum = 0 #묶음 구슬의 개수와 값을 초기화시켜준다
            cnt = 0
        # 그런 뒤 다시 값과 개수를 늘려주면 그리디 알고리즘 완료
        sum += n_lst[i]
        cnt += 1
    #반복문의 마지막은 값과 개수를 늘려주는 것으로 끝나므로 마지막에 추가해준 값과 개수까지 append로 따로 넣어준다.
    sum_lst.append(sum)
    cnt_lst.append(cnt)

    if len(sum_lst) <= M: # 만들어진 개수가 M보다 작거나 같다면 해당 값으로 묶음을 만들 수 있다

        end = mid - 1 # 좀 더 작은 최댓값으로도 묶음을 만들 수 있는지 체크하기 위해 end = mid - 1

        if max(sum_lst) < min_ans:
            min_ans = max(sum_lst)
            min_cnt = cnt_lst
    else:
        start = mid + 1
print(min_ans)
i = 0

while len(min_cnt) != M: #묶음의 개수가 같아질 때 까지
   if min_cnt[i] == 1 : # 묶음 안의 구슬 개수가 1개일 경우
       i += 1 #다음 구슬 체크
   else : #묶음 안의 구슬 개수가 1개를 초과할 경우
       min_cnt[i] -= 1 #해당 묶음의 구슬 개수를 하나 줄이고
       min_cnt.insert(i+1, 1) #그 묶음의 바로 앞에 추가로 구슬 묶음을 만들어준다.
print(*min_cnt)

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

 

17069번: 파이프 옮기기 2

유현이가 새 집으로 이사했다. 새 집의 크기는 N×N의 격자판으로 나타낼 수 있고, 1×1크기의 정사각형 칸으로 나누어져 있다. 각각의 칸은 (r, c)로 나타낼 수 있다. 여기서 r은 행의 번호, c는 열의

www.acmicpc.net

 

 

분명 BFS로 파이프 옮기기1은 BFS로 풀 수 있는 문제라고 하셨는데...... 결국 DP로 접근해서 풀고,

 

DP로 풀었으니, 파이프 옮기기2도 가뿐하게 풀 수 있었기에 뿌듯했습니다.

 

하지만 DP로 어떻게 접근해야 할 지 생각하느라 잠을 설친걸 생각하면.. 아직 멀었다는 생각이 듭니다

 

그래도 혼자 생각해낸게 어디냐며 굉장히 뿌듯하게 풀은 문제입니다.

 

** 소스코드는 더보기를 클릭해서 확인해주세요 **

 

 

1. DP 접근의 시작

  • ㅡ 모양은 0, \ 모양은 1, ㅣ 모양은 2의 상태로 두었습니다.
  • 0에서는 0과 1로 갈 수 있지만 2로 갈 수 없고, 1은 0과 1 2 모두 갈 수 있고, 2는 2와 1로 갈 수 있습니다
  • 처음엔 무턱대고 이전 값의 경우의수를 체크하여 더해주려고 했지만 이전 값의 경우의수가 있더라도 내 칸으로 올 수 없다는 경우가 발생한다는 걸 깨달았습니다. (내 왼쪽에 파이프가 올 수 있는 경우의 수가 있지만 그 파이프의 상태가 2라면? 오른쪽으로는 못오잖아..?) 
  • 그래서 파이프의 상태까지 담아줄 방법을 고민하게 됩니다.

 

더보기
N = int(input())

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

#경우의수를 담아줄 memo리스트 추가
#그냥 경우의수를 체크하는게 아니라 어떤 상태로 현재 값으로 올 수 있는지를 체크해야한다
# 왜냐면 현재 값에서 0상태로 몇개 1상태로 몇개 2상태로 몇개 올 수 있는지 체크해야만
# 현재값으로 올 수 없는 경우의수가 있기 때문
# i, j좌표에 각 상태별 경우의수를 담는 배열이 하나더 필요하므로 i,j,k 세개의 배열이 필요할 것 같다

memo = [[[0] * 3 for _ in range(N)] for _ in range(N)]

for c in range(N-1): #첫째줄에는 모두 0상태로 들어갈 수 있음
    if board[0][c+1] != 1 :
        memo[0][c+1][0] += 1
    else :
        break

 

 

2. 각 칸에 올 수 있는 조건 확인하기

  • 다시 처음으로 돌아가 생각해보았는데, 생각해보니 첫 번째 줄에 파이프가 ㅡ 모양(0상태)으로밖에 들어가지 못한다는걸 발견합니다.
  • 그것을 발견하고 나니 2번째 칸에는 \(1)모양으로밖에 갈 수 없다는 것도 보이구요
  • 또 보니까, 0열과 1열은 첫째줄을 제외하면 사용하지도 못합니다.
  • 아!! 각 상태별로 조건들이 보입니다
  • 각 칸은 올 수 있는 상태가 정해져있고, 또 1번 상태의 경우에는 이동할 칸의 위와 왼쪽이 비어있어야합니다.
  • 모든 조건들을 다 적어주도록 합니다. (1부터 시작할 것이기 때문에 사실 2, 3번째 조건은 필요 없는 조건이긴 하지만요)
더보기
# 다음부터는 내 왼쪽과, 위, 그리고 왼쪽 위의 값에 따라 자동으로 경우의수가 증가
for i in range(1, N):
    for j in range(1, N):
        if board[i][j] == 0:
            #왼쪽 위가 존재하는데 상태가 0,1,2이라면 1상태로 올 수 있음
            #단 1상태로 오려면 무조건 올 수 있는게 아니라 위와 왼쪽 공간이 비어있어야함
            if 0 <= i -1 < N and 0 <= j -1 < N and board[i-1][j] == 0 and board[i][j-1] == 0:
                pass
            #위가 존재하는데 1이나 2상태라면 2로 올 수 있음
            if 0 <= i -1 < N :
                pass
            #왼쪽이 존재하는데 0이나 1상태라면 0으로 올 수 있음
            if 0 <= j -1 < N :
                pass

 

3. 경우의수 체크

  • 이제 각 칸들이 들어올 수 있는 조건들을 체크해주었으니 상태값에 따른 경우의수를 넣어주어야 합니다.
  • 만약 왼쪽 칸에 파이프가 올 수 있는 경우의 수가 존재했더라도 그 파이프가 2상태라면 올 수 없으니까요.
  • 모든 칸의 경우의수를 상태별로 나누어 놓았으니 내 칸으로 올 때 어떤 상태로 오는지도 같이 넣어줍니다.
  • 파이프가 왼쪽에 있다면 0가 1상태인 파이프가 0상태인 파이프로 변해서 제 자리로 올 수 있을것이고
  • 파이프가 왼쪽위에 있다면 0, 1, 2상태의 파이프가 1상태인 파이프로 변해서 올 수 있을 것이고
  • 파이프가 위에 있다면 1과 2상태의 그래프가 2상태인 파이프로 변해서 올 수 있겠죠
더보기
# 다음부터는 내 왼쪽과, 위, 그리고 왼쪽 위의 값에 따라 자동으로 경우의수가 증가
for i in range(1, N):
    for j in range(1, N):
        if board[i][j] == 0:
            #왼쪽 위가 존재하는데 상태가 0,1,2이라면 1상태로 올 수 있음
            #단 1상태로 오려면 무조건 올 수 있는게 아니라 위와 왼쪽 공간이 비어있어야함
            if 0 <= i -1 < N and 0 <= j -1 < N and board[i-1][j] == 0 and board[i][j-1] == 0:
                memo[i][j][1] += memo[i-1][j-1][0] + memo[i-1][j-1][1] + memo[i-1][j-1][2]
            #위가 존재하는데 1이나 2상태라면 2로 올 수 있음
            if 0 <= i -1 < N :
                memo[i][j][2] += memo[i-1][j][1] + memo[i-1][j][2]
            #왼쪽이 존재하는데 0이나 1상태라면 0으로 올 수 있음
            if 0 <= j -1 < N :
                memo[i][j][0] += memo[i][j-1][0] + memo[i][j-1][1]

이제 마지막 칸까지 도착한 상태별 경우의수를 모두 더해주면 답이 됩니다.

 

전체 코드

 

더보기
N = int(input())

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

#경우의수를 담아줄 memo리스트 추가
#그냥 경우의수를 체크하는게 아니라 어떤 상태로 현재 값으로 올 수 있는지를 체크해야한다
# 왜냐면 현재 값에서 0상태로 몇개 1상태로 몇개 2상태로 몇개 올 수 있는지 체크해야만
# 현재값으로 올 수 없는 경우의수가 있기 때문
# i, j좌표에 각 상태별 경우의수를 담는 배열이 하나더 필요하므로 i,j,k 세개의 배열이 필요할 것 같다

memo = [[[0] * 3 for _ in range(N)] for _ in range(N)]

for c in range(N-1): #첫째줄에는 모두 0상태로 들어갈 수 있음
    if board[0][c+1] != 1 :
        memo[0][c+1][0] += 1
    else :
        break

# 다음부터는 내 왼쪽과, 위, 그리고 왼쪽 위의 값에 따라 자동으로 경우의수가 증가
for i in range(1, N):
    for j in range(1, N):
        if board[i][j] == 0:
            #왼쪽 위가 존재하는데 상태가 0,1,2이라면 1상태로 올 수 있음
            #단 1상태로 오려면 무조건 올 수 있는게 아니라 위와 왼쪽 공간이 비어있어야함
            if 0 <= i -1 < N and 0 <= j -1 < N and board[i-1][j] == 0 and board[i][j-1] == 0:
                memo[i][j][1] += memo[i-1][j-1][0] + memo[i-1][j-1][1] + memo[i-1][j-1][2]
            #위가 존재하는데 1이나 2상태라면 2로 올 수 있음
            if 0 <= i -1 < N :
                memo[i][j][2] += memo[i-1][j][1] + memo[i-1][j][2]
            #왼쪽이 존재하는데 0이나 1상태라면 0으로 올 수 있음
            if 0 <= j -1 < N :
                memo[i][j][0] += memo[i][j-1][0] + memo[i][j-1][1]

print(sum(memo[N-1][N-1]))

+ Recent posts