Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 176 additions & 0 deletions studies/week-02/min9u/26071-블록 제거 게임.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
# [SWEA 1767] 프로세서 연결하기

| 항목 | 내용 |
| :----------- | :--------------------------------------------------------------------------------------------- |
| 🔗 문제 | https://swexpertacademy.com/main/solvingProblem/solvingProblem.do |
| 📚 주제 | DP |
| 🎚️ 난이도 | D4 |
Comment on lines +1 to +7

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

문제 식별자와 풀이 내용이 일치하지 않습니다.

PR 목적과 구현은 블록 제거 게임인데 제목은 SWEA 1767 프로세서 연결하기로 되어 있습니다. 제목을 실제 문제 식별자로 수정하고, 문제 링크도 해당 문제의 직접 링크로 교체해주세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@studies/week-02/min9u/26071-블록` 제거 게임.md around lines 1 - 7, 문서 제목과 문제 링크가 실제
구현 대상인 블록 제거 게임과 일치하도록 수정하세요. 제목의 SWEA 문제 식별자를 블록 제거 게임의 식별자로 변경하고, 현재 일반 풀이 링크를
해당 문제의 직접 링크로 교체하세요.

Source: Path instructions

| ⏱️ 소요 시간 | 2시간 30분 |
| 🔁 다시 풀기 | 예 |

---

## 1. 접근 방법
<!-- 문제를 어떻게 이해했고, 왜 이 전략을 택했는지. 처음 떠올린 방법과 막혔던 지점도 좋음. -->
처음에 쉽게 아이디어가 생각나지 않아 단순히 완전탐색으로 풀어보았습니다. (주석에 추가)
시간초과로 DP 아이디어를 생각해 풀어보았습니다.

## 2. 시간 복잡도
<!-- 예: O(N log N) — 정렬 O(N log N) + 순회 O(N). 근거를 함께 적기. -->
<!-- SWEA는 시간 제한이 빡빡한 문제가 많으니 반드시 따져볼 것! -->
**O(T × N³)**

리스트 앞뒤에 1을 붙여 n+2개 원소로 만든 뒤, dp[i][j] 형태의 구간을 정의합니다.
가능한 구간 (i, j)의 개수는 O(N²)개이고, 각 구간마다 "마지막으로 깨지는 블록 k"를 O(N)개 시도하며 값을 구합니다.
메모이제이션(dp[a][b] != -1 체크)으로 한 번 계산된 구간은 재계산하지 않으므로, 전체 계산량은 **구간 수(O(N²)) × 구간당 전이 비용(O(N)) = O(N³)**입니다.
Comment on lines +23 to +25

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Markdown lint 경고를 수정해주세요.

dp[i][j], dp[a][b]는 백틱으로 감싸고, ## 5. 실제 코드 앞에는 빈 줄을 추가해야 합니다.

Also applies to: 45-45

🧰 Tools
🪛 markdownlint-cli2 (0.23.0)

[warning] 23-23: Reference links and images should use a label that is defined
Missing link or image reference definition: "j"

(MD052, reference-links-images)


[warning] 25-25: Reference links and images should use a label that is defined
Missing link or image reference definition: "b"

(MD052, reference-links-images)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@studies/week-02/min9u/26071-블록` 제거 게임.md around lines 23 - 25, 수식 표현인
dp[i][j]와 dp[a][b]를 모두 인라인 코드 백틱으로 감싸고, `## 5. 실제 코드` 헤딩 바로 앞에 빈 줄을 추가해 Markdown
lint 경고를 해결하세요.

Sources: Path instructions, Linters/SAST tools

테스트케이스가 T개이므로 총 시간복잡도는 **O(T × N³)**이며, N≤10, T≤50 제약에서는 최대 약 50 × 10³ = 50,000번 연산 수준이라 충분히 빠릅니다.
(참고: 완전탐색은 O(N!)이었던 것과 비교하면, N=10 기준 3,628,800배 → 1,000배 수준으로 극적으로 줄어든 셈입니다.)
Comment on lines +21 to +27

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

복잡도는 맞지만 50,000번 연산 계산은 부정확합니다.

경계값을 추가한 크기는 M = N + 2이고, 실제 전이 후보 수는

Σ(M-L)(L-1) (L=3..M-1)로, N=10일 때 테스트케이스당 210회입니다. 재귀 호출과 메모이제이션 조회까지 포함한 전체 연산 수를 50 × 10³으로 단정하지 말고, O(T×N³) 및 TLE 위험이 낮다는 정도로 서술하는 편이 정확합니다.

🧰 Tools
🪛 markdownlint-cli2 (0.23.0)

[warning] 23-23: Reference links and images should use a label that is defined
Missing link or image reference definition: "j"

(MD052, reference-links-images)


[warning] 25-25: Reference links and images should use a label that is defined
Missing link or image reference definition: "b"

(MD052, reference-links-images)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@studies/week-02/min9u/26071-블록` 제거 게임.md around lines 21 - 27, 복잡도 설명에서
`50,000번 연산` 및 정확한 연산량·감소 배수 단정을 제거하세요. 경계값을 포함한 전이 후보 수와 재귀 호출·메모이제이션 조회까지 고려하면
해당 수치는 부정확하므로, `O(T × N³)` 복잡도와 주어진 제약에서 TLE 위험이 낮다는 내용만 유지해 서술을 수정하세요.

Source: Path instructions


## 3. 공간 복잡도
<!-- 예: O(N) — 방문 배열 N개. -->
**O(N²)**

dp 테이블이 (n+2) × (n+2) 크기의 2차원 배열이므로, 저장 공간은 O(N²)입니다.
추가로 재귀 호출에 따른 콜스택 깊이가 있지만, 이는 최대 구간 크기에 비례하는 O(N) 수준이라 전체 공간복잡도를 지배하지 않습니다.

## 4. 핵심 풀이 방법 ⭐
<!-- 이 문제의 결정적 아이디어 / 자주 하는 실수 / 알아야 할 개념. 가장 중요한 항목! -->
1. 핵심 아이디어는 **마지막으로 깨지는 블록** 만 고려하면 간단한 DP 문제가 된다는 것입니다. 처음 문제를 직관적으로 보았을 때에는 블록들이 깨지는 순서를 모두 고려해야 할 것 같지만 그렇지 않습니다.
**특정 구간 내**(i,j)에서의 최대 점수를 DP(i,j)라고 정의하고, **마지막으로 깨지는 블록**을 k라고 정의하면 다음과 같이 부분식을 만들 수 있습니다.
DP(i,j) = DP(i,k) + DP(k,j) + arr[i]*arr[j]
마지막에 깨지는 블록이 무엇이 되든 마지막 점수는 arr[i]*arr[j]가 됩니다.

2. 입력된 array 처음과 끝에 원소 1을 추가해 문제를 더 단순히 만들었습니다. 좋은 아이디어라 다른 문제에서도 응용될 것 같습니다
3. 마지막으로 깨지는 블록은 cost가 arr[i]*arr[j]가 아니라 arr[k] 것을 주의해야합니다.
Comment on lines +38 to +44

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

점화식 설명을 실제 코드의 두 경우로 분리해주세요.

현재 설명은 arr[i]*arr[j]arr[k]를 혼용해 모순됩니다. 정확한 설명은 다음과 같아야 합니다.

  • 일반 구간: max(cost(a,k) + cost(k,b) + li[a] * li[b])
  • 전체 구간: 마지막에 남는 블록 k의 점수 li[k]를 더해 max(cost(0,k) + cost(k,n-1) + li[k])
  • arr[i][j]는 잘못된 표기이므로 li[a] * li[b] 등 실제 변수명으로 수정

이 부분은 독자가 설명만 보고 구현할 때 오답을 만들 수 있습니다.

수정 예시
- dp[i][j] = max(dp[i][k] + dp[k][j] + arr[i][j])
+ cost(a, b) = max(cost(a, k) + cost(k, b) + li[a] * li[b])
+ answer = max(cost(0, k) + cost(k, n - 1) + li[k])

Also applies to: 48-57

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@studies/week-02/min9u/26071-블록` 제거 게임.md around lines 38 - 44, Update the DP
explanation to distinguish the general interval recurrence from the
full-interval recurrence: use max(cost(a,k) + cost(k,b) + li[a] * li[b]) for
ordinary intervals, and max(cost(0,k) + cost(k,n-1) + li[k]) for the whole
interval. Replace incorrect arr[i][j] or arr[k] notation with the actual
li-based variable names consistently.

Source: Path instructions

## 5. 실제 코드

```python
"""
DP를 사용할 수 있는 조건 :문제를 부분문제들로 나눠서 통합할수 있는가
dp[i][j] = max(dp[i][k] + dp[k][j] + arr[i][j])
dp[i][i+2] = arr[i]*arr[i+2]
dp[i][i+1] = 0

입력받은 리스트의 앞과 뒤에 원소 1을 추가하여 계산이 용이하게 만들었다.
예외조건으로 마지막으로 제거되는 블록은 자기 점수를 반환하는 것을 주의해야한다.

하향식으로 구현하여 dp[0][n+1]을 최종적으로 반환하는 함수를 구현한다
"""
T = int(input())
for tc in range(1, T+1):
n = int(input())
li = list(map(int ,input().split()))
# 입력받은 리스트의 앞과 뒤에 원소 1을 추가하여 계산이 용이하게 만듬
li = [1] + li + [1]
n+=2

dp = [[-1]*n for _ in range(n)]

def cost(a,b):
# 이미 계산된 함수값은 배열에서 찾아서 반환
if dp[a][b] != -1:
return dp[a][b]
# 최소단위의 부분 문제들
if b-a == 1:
dp[a][b] = 0
return 0
if b-a == 2:
dp[a][b] = li[a]*li[b]
return dp[a][b]

# 부분문제들로 나눈다
max_score = 0
for k in range(a+1, b):
# !! 마지막으로 깨지는 블록 예외처리
if a==0 and b==n-1:
score = cost(a,k) + cost(k,b) + li[k]
else:
score = cost(a,k) + cost(k,b) + li[a]*li[b]
max_score = max(max_score, score)
# dp 배열에 이미 계산한 값을 저장해, 두번이상 같은 함수를 계산하지 않는다
dp[a][b] = max_score
return max_score

ans = cost(0,n-1)
# !! 리스트 원소가 하나일 때 예외처리
if n==3:
ans = li[1]
print(f'#{tc} {ans}')


"""
완전탐색으로 구현해보고 시간 초과남 => dp로 전환
"""
# T = int(input())
# for tc in range(1, T+1):
# n = int(input())
# li = list(map(int, input().split()))

# max_score = 0
# score = 0
# def dfs():
# global max_score, score, li # li는 global로 넣어야할지 말아야할지 헷갈린다
# for idx, val in enumerate(li):
# if val == 0:
# continue
# l_idx, r_idx = idx,idx
# l_value, r_value = 0,0
# while True:
# l_idx -= 1
# if l_idx<0:
# break
# l_value = li[l_idx]
# if l_value>0:
# break
# while True:
# r_idx += 1
# if r_idx>=len(li):
# break
# r_value = li[r_idx]
# if r_value>0:
# break

# # 이웃 없음
# if r_value == 0 and l_value == 0:
# score += val
# max_score = max(max_score, score)
# score -= val
# return

# # 한쪽만 이웃
# elif r_value == 0 and l_value:
# score += l_value
# li[idx] = 0
# dfs()
# li[idx] = val
# score -= l_value

# elif l_value == 0 and r_value:
# score += r_value
# li[idx] = 0
# dfs()
# li[idx] = val
# score -= r_value

# # 둘다 이웃
# else:
# score += l_value*r_value
# li[idx] = 0
# dfs()
# score -= l_value*r_value
# li[idx] = val

# dfs()
# print(f"#{tc} {max_score}")

```

---

### 🧠 회고 (선택)

<!-- 배운 점, 비슷한 유형에서 재사용할 아이디어, 리뷰어에게 묻고 싶은 점 -->
DP로 푸는 아이디어가 먼저 떠오르지 않아 완전탐색으로 구현한 후 시간초과가 나왔습니다.
DP로 풀수 있는 문제들(부분문제들도 나눠서 통합)을 더 풀어보고 직관적으로 DP로 풀수있는 아이디어가 나와야 시간을 크게 소요하지 않을 것 같습니다.

예외처리 코드가 예쁘지 않아서 더 좋은 예외처리 코드는 무엇일지 같이 고민해주시면 좋겠습니다.
Loading