diff --git "a/studies/week-02/Suyoung-Min/26070_\353\263\264\354\204\235\354\210\230\354\247\221\353\241\234\353\264\207.md" "b/studies/week-02/Suyoung-Min/26070_\353\263\264\354\204\235\354\210\230\354\247\221\353\241\234\353\264\207.md" new file mode 100644 index 0000000..a68c7f5 --- /dev/null +++ "b/studies/week-02/Suyoung-Min/26070_\353\263\264\354\204\235\354\210\230\354\247\221\353\241\234\353\264\207.md" @@ -0,0 +1,85 @@ +# [SWEA 26070] 보석 수집 로봇 + +| 항목 | 내용 | +|:---|:---| +| 🔗 문제 | https://swexpertacademy.com/main/code/userProblem/userProblemDetail.do?contestProbId=AZwmBfua3q3HBIT3 | +| 📚 주제 | BFS, 다익스트라 | +| 🎚️ 난이도 | D4 | +| ⏱️ 소요 시간 | 1H | +| 🔁 다시 풀기 | 예 | + +--- + +## 1. 접근 방법 +로봇이 `(0,0)`에서 오른쪽을 보고 출발해, **보석을 번호 오름차순으로** 하나씩 방문한다. +비용은 **회전 횟수**뿐 — 전진은 0, 90° 회전은 +1. +"보석 사이 구간마다 최소 회전 수"를 각각 구해 누적하면 되므로, +각 구간을 **비용이 0/1인 최단경로(다익스트라)** 로 푼다. 도착 시 위치·방향을 다음 구간으로 넘긴다. + +## 2. 시간 복잡도 +**O(J · n² log n)** — J = 보석 수(최대 ~n²), 구간마다 상태 `(y,x,방향,회전가능)` 다익스트라. +전체적으로는 대략 `O(n⁴ log n)` 규모. + +## 3. 공간 복잡도 +**O(n²)** — 격자 저장 및 힙에 올라가는 상태 수(위치×방향×플래그 = n²×4×2). + +## 4. 핵심 풀이 방법 ⭐ +- **상태를 `(비용, y, x, 방향, 회전가능여부)`** 로 정의해 힙에 넣는다. +- **비용 = 회전 수**: 전진은 `cost` 그대로, 회전은 `cost+1`. → 0/1 가중치라 다익스트라가 성립. +- **연속 회전 금지**: `turn_flag`로 제어. 회전하면 `False`(다음엔 회전 불가), 전진하면 `True`(회전 가능) → 제자리 180° 방지, 회전 사이 최소 1칸 전진 강제. +- **시작 제약**: 맨 처음 출발점에서는 회전 불가(무조건 전진), 이후 보석 지점에서는 회전 1회 가능. +- 목표 보석을 **힙에서 처음 꺼내는 순간**이 최소 비용(다익스트라 성질) → 그때 위치·방향 갱신 후 다음 보석으로. + +## 5. 실제 코드 +```python +from collections import deque +import heapq + +T = int(input()) +for tc in range(1, T + 1): + n = int(input()) + grid = [list(map(int, input().split())) for _ in range(n)] + + d = [(-1, 0), (0, 1), (1, 0), (0, -1)] # 상 우 하 좌 + num_of_turn = 0 + + # 내부 격자에서 보석 수집 후 번호 오름차순 정렬 + jewels = [(y, x) for y in range(1, n-1) for x in range(1, n-1) if grid[y][x]] + jewels.sort(key=lambda cor: grid[cor[0]][cor[1]]) + jewels = deque(jewels) + + sy, sx = 0, 0 # 시작 좌표 + cur_d = 1 # 시작 방향: 오른쪽 + first_flag = True + + while jewels: + ey, ex = jewels.popleft() + q = [] + # 최초 출발은 회전 불가(False), 이후 보석 출발은 회전 1회 가능(True) + heapq.heappush(q, (0, sy, sx, cur_d, not first_flag)) + first_flag = False + + while q: + cost, y, x, direct, turn_flag = heapq.heappop(q) + + if y == ey and x == ex: # 목표 보석 도착 + sy, sx = y, x + num_of_turn += cost # 회전 수 누적 + cur_d = direct + break + + if turn_flag: # 회전 (다음엔 회전 불가) + heapq.heappush(q, (cost+1, y, x, (direct+1) % 4, False)) + + ny, nx = y + d[direct][0], x + d[direct][1] # 전진 (다음엔 회전 가능) + if 0 <= ny < n and 0 <= nx < n: + heapq.heappush(q, (cost, ny, nx, direct, True)) + + print(f'#{tc} {num_of_turn}') +``` + +--- + +### 🧠 회고 +- cost 가 일반적인 다익스트라와는 다르게 **회전 횟수** 인 것만 주의하면 어렵지 않다. +- 개선 여지: `visited` 을 활용하지 않았는데, 이로 인해 중복이 좀 있을 것 같다. 개선해보려 한다. diff --git "a/studies/week-02/Suyoung-Min/26071_\353\270\224\353\241\235\354\240\234\352\261\260\352\262\214\354\236\204.md" "b/studies/week-02/Suyoung-Min/26071_\353\270\224\353\241\235\354\240\234\352\261\260\352\262\214\354\236\204.md" new file mode 100644 index 0000000..5356e44 --- /dev/null +++ "b/studies/week-02/Suyoung-Min/26071_\353\270\224\353\241\235\354\240\234\352\261\260\352\262\214\354\236\204.md" @@ -0,0 +1,151 @@ +# [SWEA 26071] 블록 제거 게임 + +| 항목 | 내용 | +|:---|:---| +| 🔗 문제 | https://swexpertacademy.com/main/code/userProblem/userProblemDetail.do?contestProbId=AZwmCVWq3uLHBIT3& | +| 📚 주제 | 구간 DP (Interval DP · Burst Balloons 계열) | +| 🎚️ 난이도 | D4 | +| ⏱️ 소요 시간 | 3h + | +| 🔁 다시 풀기 | 예 | + +--- + +## 1. 접근 방법 +"어떤 순서로 깰까"를 정방향으로 탐색하면 순열 `O(N!)`이라 TLE. +발상을 뒤집어 "이 구간에서 *마지막*에 깨지는 블록이 누구인가"를 기준으로 잡는다. +마지막에 깨지는 블록 `k`는 그 순간 이웃이 구간 양 끝뿐이라 점수가 확정되고, +`k`를 기준으로 좌·우 구간이 **완전히 독립**으로 갈라진다 → 구간 DP로 접힌다. + +## 2. 시간 복잡도 +**O(N³)** — 구간 폭 × 왼쪽 경계 × 분할점 `k`. 순열 `O(N!)`에서 크게 개선. + +## 3. 공간 복잡도 +**O(N²)** — `dp[l][r]` 2차원 테이블. + +## 4. 핵심 풀이 방법 ⭐ +- 양 끝에 값 `1` **센티넬**을 붙인다 → "이웃 한쪽만 있을 때 그 값만큼"이 `1×이웃값`으로 자동 처리. +- `dp[l][r]` = 경계 `l`, `r`는 **벽으로 남긴 채** 그 *사이* 블록을 전부 깰 때의 최대 점수. + - `dp[l][r] = b[l] * b[r] + max( dp[l][k] + dp[k][r] )` (`l < k < r`) + - `k`를 마지막에 깨는 순간의 점수가 `b[l] * b[r]` (자기 값은 안 들어감). +- **마지막 한 블록**은 이웃이 없어 자기 값을 얻음 → DP로 못 접히는 예외라 밖에서 처리: + - 마지막 블록 `p`를 고정해 `dp[0][p] + dp[p][n+1] + b[p]`의 최댓값이 정답. + +## 5. 실제 코드 +```python +T = int(input()) +for tc in range(1, T+1): + + N = int(input()) + arr = list(map(int ,input().split())) # N + + b = [1] + arr + [1] # 0 ~ n+1 # 1 + 0 ~ n-1 + 1 + + M = N + 2 + + dp = [[0]*(M) for _ in range(M)] + + for length in range(2, M): + + for l in range(M - length): + r = l + length + + best = dp[l][l+1] + dp[l+1][r] + + for k in range(l+1, r): + best = max(dp[l][k] + dp[k][r], best) + + dp[l][r] = b[l] * b[r] + best + + ans = max(dp[0][p] + dp[p][N+1] + b[p] for p in range(1, N+1)) + + print(f'#{tc} {ans}') + +## 백트래킹 - 시간초과 + +# T = int(input()) +# # 여러개의 테스트 케이스가 주어지므로, 각각을 처리합니다. +# for test_case in range(1, T + 1): +# # /////////////////////////////////////////////////////////////////////////////////// + +# n = int(input()) + +# arr = list(map(int, input().split())) + +# ans_score = 0 + +# # 삭제된 거 -> 0 +# # 순서 있는 순열 +# # arr[i] = 0 으로 방문처리 + +# def backtrack(cidx, score, remain_block): +# global ans_score + +# #print(f'cidx: {cidx}, score: {score}, remain_block: {remain_block}') + +# # 종료 조건 +# if remain_block == 0: # 블록 탐색 완료 +# ans_score = max(ans_score, score) +# return + +# if arr[cidx] == 0: # 부숴진 블록이면 +# return + +# # 현재 위치는 항상 온전한 블록 +# # 현재 위치 기준 오른쪽 왼쪽 이웃 있는지 + +# left_idx = -1 +# right_idx = -1 + +# left_neighbor = False +# right_neighbor = False + +# for lidx in range(cidx-1, -1, -1): +# if arr[lidx]: # 안부숴진 블록이면 +# left_idx = lidx +# left_neighbor = True +# break + +# for ridx in range(cidx+1, n): +# if arr[ridx]: # 안부숴진 블록이면 +# right_idx = ridx +# right_neighbor = True +# break + +# if left_neighbor and right_neighbor: +# score += arr[left_idx] * arr[right_idx] + +# elif left_neighbor: +# score += arr[left_idx] + +# elif right_neighbor: +# score += arr[right_idx] + +# else: # 이웃없을 때 +# score += arr[cidx] + +# save_block = arr[cidx] +# for i in range(n): +# arr[cidx] = 0 + +# backtrack(i, score, remain_block - 1) + +# arr[cidx] = save_block + +# for s in range(n): +# backtrack(s, 0, n) + +# print('#'+str(test_case), ans_score) + +# # /////////////////////////////////////////////////////////////////////////////////// +``` + +--- + +### 🧠 회고 +- "정방향으로 안 쪼개지면 **마지막(또는 처음) 하나를 고정**해 양쪽을 독립시킨다"가 구간 DP의 핵심 발상. +- 센티넬 `1`로 경계 예외를, `+ b[p]`로 마지막 블록 예외를 처리 — **DP로 못 접는 예외는 밖에서 챙긴다.** +- 순열 풀이는 N이 작을 때만 통과. 근본 해결은 구간 DP `O(N³)`. +- 백트래킹으로는 시간 제한이 걸릴 거라 생각했지만, 당연히 터졌다. +- DP 는 방식을 떠올리지 않으면 너무 풀기 어려워서 평소에 DP 연습을 좀 진행해야 할 듯 하다. +- 이번 문제는 풀기 못해서 CLAUDE 에게 시켰다... +- 같은 구간 DP 문제인 BURST BALLOONS 문제도 같이 연습할만하다. diff --git "a/studies/week-02/Suyoung-Min/5656_\353\262\275\353\217\214\352\271\250\352\270\260.md" "b/studies/week-02/Suyoung-Min/5656_\353\262\275\353\217\214\352\271\250\352\270\260.md" new file mode 100644 index 0000000..77ef445 --- /dev/null +++ "b/studies/week-02/Suyoung-Min/5656_\353\262\275\353\217\214\352\271\250\352\270\260.md" @@ -0,0 +1,130 @@ +# [SWEA] 벽돌 깨기 + +| 항목 | 내용 | +|:---|:---| +| 🔗 문제 | https://swexpertacademy.com/main/code/problem/problemDetail.do?contestProbId=AWXRQm6qfL0DFAUo | +| 📚 주제 | 완전탐색(DFS) · 시뮬레이션(연쇄 폭발 BFS) · 중력 | +| 🎚️ 난이도 | 중상 | +| ⏱️ 소요 시간 | 1H | +| 🔁 다시 풀기 | 예 | + +--- + +## 1. 접근 방법 +구슬 하나를 떨어뜨릴 때 **어느 열에 떨어뜨릴지** 매번 W가지 선택 → 총 N번 → 모든 경우를 **DFS 완전탐색**. +각 선택마다 (1) 그 열 맨 위 벽돌을 터뜨리고 → (2) 숫자만큼 상하좌우로 **연쇄 폭발** → (3) **중력**으로 벽돌 낙하 → (4) 다음 구슬로 재귀. +구슬을 다 쓰거나 벽돌이 0이 되면 남은 벽돌 수를 **최솟값에 갱신**. + +## 2. 시간 복잡도 +**O(Wᴺ · W·H)** — 매 단계 W개 열 분기 × 깊이 N, 각 노드마다 그리드 복사·폭발·중력이 O(W·H). +N·W가 작아 완전탐색으로 통과 (모의 역량테스트 제약 기준). + +## 3. 공간 복잡도 +**O(N · W·H)** — 재귀 깊이 N만큼 그리드 복사본을 스택에 유지. + +## 4. 핵심 풀이 방법 ⭐ +- **분기**: 각 열에서 맨 위 벽돌 하나를 찾아(없으면 skip) 그 상태를 복사해 폭발 시뮬레이션. +- **연쇄 폭발(BFS)**: 큐에 터질 벽돌을 넣고, 벽돌 값 `block_size`만큼 4방향 거리 `1..block_size-1`을 훑어 벽돌이면 큐에 추가 → 연쇄 처리. +- **중력**: 각 열을 아래에서부터 채워 빈칸 없이 정렬(`gravity_fall`). +- **종료·갱신**: 벽돌 합이 0이면 0으로 확정, 구슬 소진 시 남은 벽돌 수를 `min`으로 갱신. + +## 5. 실제 코드 +```python +import copy +from collections import deque + +T = int(input()) +for tc in range(1, T + 1): + N, W, H = map(int, input().split()) + + arr = [list(map(int, input().split())) for _ in range(H)] + + d = [(-1, 0), (0, 1), (1, 0), (0, -1)] + + remain_blocks = int(1e10) + def gravity_fall(grid): + for x in range(W): + col = [grid[y][x] for y in range(H) if grid[y][x]] + col = [0] * (H - len(col)) + col + for y in range(H): + grid[y][x] = col[y] + return grid + + # def gravity_fall(grid): + + # h = len(grid) + # w = len(grid[0]) + + # for x in range(w): + # tarr = [0]*h + # tidx = h-1 + + # for y in range(h-1, -1, -1): + # if grid[y][x]: # 있으면 + # tarr[tidx] = grid[y][x] + # tidx -= 1 + + # for y in range(h): + # grid[y][x] = tarr[y] + + # return grid + + def dfs(remain_balls, grid): + global remain_blocks + + if sum(sum(row) for row in grid) == 0: + remain_blocks = 0 + return + + if remain_balls == 0: + remain_blocks = min(sum([grid[y][x] != 0 for y in range(H) for x in range(W)]), remain_blocks) + return + for fall_x in range(W): + + find_block = False + + bq = deque([]) + + for y in range(H): + if grid[y][fall_x]: # 블록이 있다면 + find_block = True + bq.append((y, fall_x)) + break + + if not find_block: continue + + # ngrid = [row[:] for row in grid] # 깊은 복사 + ngrid = copy.deepcopy(grid) + + # 2. 블록 부수기 + + while bq: + by, bx = bq.popleft() + + block_size = ngrid[by][bx] + ngrid[by][bx] = 0 + + for size in range(1, block_size): + for dy, dx in d: + ny = by + dy * size + nx = bx + dx * size + + # 그리드 안이고, 블록이면 + if (0 <= ny < H and 0 <= nx < W) and ngrid[ny][nx]: + bq.append((ny, nx)) + + # 블록 부쉈으니 gravity + ngrid = gravity_fall(ngrid) + + dfs(remain_balls-1, ngrid) + + dfs(N, arr) + print(f'#{tc} {remain_blocks}') +``` + +--- + +### 🧠 회고 +- "선택 분기(DFS) + 상태 복사 + 시뮬레이션(폭발·중력)" 의 결합. +- 상태를 복사해 분기하므로 각 재귀가 서로 간섭하지 않는다. 복사 방식(`copy.deepcopy` vs 행 복사)을 알아두자. +- 중력 처리 부분 `gravity_fall` 함수는 CLAUDE 가 추천한 코드다. 주석처리한 건 본인 코드인데, CLAUDE의 중력 함수가 좀 더 실수가 적을 것 같다. 3차원에서의 중력 적용도 생각해볼만 하다. diff --git "a/studies/week-02/Suyoung-Min/5658_\353\263\264\353\254\274\354\203\201\354\236\220\353\271\204\353\260\200\353\262\210\355\230\270.md" "b/studies/week-02/Suyoung-Min/5658_\353\263\264\353\254\274\354\203\201\354\236\220\353\271\204\353\260\200\353\262\210\355\230\270.md" new file mode 100644 index 0000000..e798f39 --- /dev/null +++ "b/studies/week-02/Suyoung-Min/5658_\353\263\264\353\254\274\354\203\201\354\236\220\353\271\204\353\260\200\353\262\210\355\230\270.md" @@ -0,0 +1,60 @@ +# [SWEA 5658] 보물상자 비밀번호 + +| 항목 | 내용 | +|:---|:---| +| 🔗 문제 | https://swexpertacademy.com/main/code/problem/problemDetail.do?contestProbId=AWXRUN9KfZ8DFAUo | +| 📚 주제 | 구현 · 시뮬레이션(회전) · 16진수 변환 · 중복 제거 | +| 🎚️ 난이도 | 중 | +| ⏱️ 소요 시간 | 30M | +| 🔁 다시 풀기 | 아니오 | + +--- + +## 1. 접근 방법 +사각 상자 둘레에 `n`개의 16진수 숫자가 배치돼 있고, 한 변은 `n/4`개씩이라 변마다 `n/4`자리 수 하나가 만들어진다. +매 회전마다 **네 변의 수 4개**를 동시에 읽으므로, `n/4`번만 돌리면 나올 수 있는 모든 수 후보가 전부 나온다. +같은 수가 중복되므로 **집합으로 중복 제거** → 10진수로 바꿔 내림차순 정렬 → **k번째 큰 값** 출력. + +## 2. 시간 복잡도 +**O(n²/4)** — `n/4`번 회전 × 회전마다 4개 변(총 `n`자리) 읽기. 이후 정렬 O(C log C), C=후보 개수. +`n`이 작아(최대 ~28) 충분히 빠름. + +## 3. 공간 복잡도 +**O(n²)** — 후보 집합 -> 길이 n/4 문자열이 최대 n 개 + +## 4. 핵심 풀이 방법 ⭐ +- 둘레 숫자를 **덱**으로 만들어 `rotate(1)`로 한 칸씩 회전. +- **핵심 최적화**: 네 변을 매 회전마다 함께 읽으므로 `n/4`번(`side`번)만 돌려도 모든 후보를 수집 → 불필요한 4배 중복 회전 제거. +- 매 회전마다 `range(0, n, side)`로 **네 변의 시작 위치**를 잡아 각 변 `side(=n/4)`자리를 읽는다. +- 읽은 문자열을 **`set`에 저장**해 중복 제거. +- 마지막에 `int(x, 16)`으로 **16진수 → 10진수** 변환, 내림차순 정렬 후 `[k-1]`. + +## 5. 실제 코드 +```python +from collections import deque + +T = int(input()) +for tc in range(1, T + 1): + n, k = map(int, input().split()) + treas = deque(input()) # 둘레 숫자를 덱으로 + + hex_count = set() # 중복 제거용 + side = n // 4 # 한 변의 자릿수 + + for _ in range(side): # !!! n/4번만 회전해도 모든 후보 확보 !!! + for i in range(0, n, side): # 네 변의 시작 위치 + num = ''.join(treas[i + j] for j in range(side)) + hex_count.add(num) + treas.rotate(1) + + hex_list = sorted((int(x, 16) for x in hex_count), reverse=True) + print(f'#{tc} {hex_list[k-1]}') +``` + +--- + +### 🧠 회고 +- "회전 시뮬레이션 → 후보 수집 → 중복 제거 → 정렬 후 k번째"의 전형적인 구현 흐름. +- 네 변을 동시에 읽는 구조라 **`n/4`번 회전이면 충분**하다는 점이 핵심 최적화 포인트. `range(n)`으로 돌리면 같은 결과를 4배 느리게 얻는다. -> 처음에는 n 번으로 돌렸다가 AI 피드백으로 깨달음, 비슷한 문제에 적용할만한 아이디어 +- 덱의 `rotate`로 회전 로직이 한 줄로 깔끔해진다. 인덱스 직접 계산보다 실수 여지가 적음. +- `int()` 로 진법변환하는 스킬은 외워두자.