-
Notifications
You must be signed in to change notification settings - Fork 0
[W01] DevJunz - SWEA 1문제 #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,171 @@ | ||
| # [SWEA 1767] 프로세서 연결하기 | ||
|
|
||
| | 항목 | 내용 | | ||
| |:---|:---| | ||
| | 🔗 문제 | https://swexpertacademy.com/main/code/problem/problemDetail.do?contestProbId=AV4suNtaXFEDFAUf | | ||
| | 📚 주제 | 완전탐색 · 백트래킹(DFS) | | ||
| | 🎚️ 난이도 | D4 | | ||
| | ⏱️ 소요 시간 | 1h 30min | | ||
| | 🔁 다시 풀기 | 예 | | ||
|
|
||
| --- | ||
|
|
||
| ## 1. 접근 방법 | ||
| `N×N` 격자에서 값이 `1`인 칸이 코어다. **가장자리(테두리)에 있는 코어는 이미 전원에 연결된 것으로 보고 무시**하고, **내부 코어만** 상·우·하·좌 중 한 방향으로 전선을 격자 밖까지 직선으로 빼야 전원에 연결된다. 전선은 다른 코어나 이미 놓인 전선과 겹칠 수 없다. | ||
|
|
||
| 목표는 두 단계다. | ||
| 1. **연결한 코어 수를 최대화** | ||
| 2. 그 최대 조건 안에서 **전선 길이 합을 최소화** | ||
|
|
||
| 내부 코어 하나하나에 대해 "4방향 중 하나로 연결" 또는 "연결하지 않음"을 고르는 완전탐색(백트래킹)으로 풀었다. 전선을 놓을 땐 격자에 `2`를 칠하고, 재귀에서 빠져나올 때 다시 `0`으로 되돌린다. | ||
|
|
||
| 처음엔 모든 코어를 무조건 연결하려 했지만, **한 코어를 연결하면 그 전선이 다른 코어의 길을 막아 전체 연결 수가 오히려 줄 수 있다.** 그래서 각 코어에서 "연결하지 않는" 분기를 반드시 함께 둬야 한다는 점이 핵심이었다. | ||
|
|
||
| ## 2. 시간 복잡도 | ||
| **O(N · 5^K)** — `K` = 내부 코어 수(최대 12), 각 코어에서 최대 5분기(4방향 + 연결 안 함). | ||
|
|
||
| - 각 분기에서 `check_path` / `set_path`가 전선을 격자 끝까지 훑으므로 분기당 `O(N)`. | ||
| - 따라서 최악 `O(N · 5^K)`. 다만 `can_go_dir` 사전 가지치기와 경로 충돌(`length == 0`) 덕분에 실제 탐색량은 이보다 크게 작다. `N ≤ 12`라 제한 시간 안에 통과. | ||
|
|
||
| ## 3. 공간 복잡도 | ||
| **O(N²)** — 격자 `cells`가 `O(N²)`로 지배적. 재귀 호출 스택 깊이는 `O(K)`, 코어 목록·방향 목록은 `O(코어 수)`. | ||
|
|
||
| ## 4. 핵심 풀이 방법 ⭐ | ||
| - **가장자리 코어 스킵**: `c[0]`이나 `c[1]`이 `0` 또는 `N-1`이면 연결 대상에서 제외. 이걸 놓치면 불필요한 분기가 폭발한다. | ||
| - **"연결 안 함" 분기 필수**: `dfs` 마지막의 `dfs(idx+1, connected, total_length)` 한 줄. 최대 연결 수를 위해 일부 코어를 일부러 포기해야 할 수 있으므로 반드시 있어야 한다. | ||
| - **최댓값 우선, 동률이면 최소 길이**: 리프에서 `connected > max_core`면 `min_length`를 새 길이로 **리셋**하고, `connected == max_core`면 `min`으로 갱신한다. 이 두 조건을 섞으면 틀린다. | ||
| - **백트래킹 복원**: `set_path(..., 2)`로 전선을 놓고 재귀한 뒤 반드시 `set_path(..., 0)`으로 되돌린다. 복원을 빠뜨리면 이후 탐색이 오염된다. | ||
| - **방향 사전 가지치기(`can_go_dir`)**: 같은 행/열에 다른 코어가 직선 경로를 막고 있으면 그 방향은 (전선 상태와 무관하게) 영구히 불가능하므로 미리 제거해 분기를 줄인다. 코어는 절대 통과할 수 없기 때문에 안전한 가지치기다. | ||
|
|
||
| ## 5. 실제 코드 | ||
| ```python | ||
| T = int(input()) | ||
| for t in range(1,T+1): | ||
|
|
||
| N = int(input()) | ||
|
|
||
| cells = [list(map(int,input().split())) for _ in range(N)] | ||
|
|
||
| answer = 0 | ||
|
|
||
|
|
||
|
|
||
| core = [] | ||
| for n in range(N): | ||
| for m in range(N): | ||
| if cells[n][m] == 1: | ||
| core.append((n,m)) | ||
|
|
||
| target_core = [] | ||
| for c in core: | ||
| # 벽에 붙어 있는 코어 스킵 | ||
| if c[0]==0 or c[0]==N-1 or c[1]==0 or c[1]==N-1: | ||
| continue | ||
| # 0부터 3까지 상우하좌라고 가정한다. | ||
| can_go_dir = [] | ||
|
|
||
| for i in range(4): | ||
| #하나의 코어당 다른 모든 코어에 대해서 검사해야한다 | ||
| flag = True | ||
| for c2 in core: | ||
| #자기 자신은 스킵 | ||
| if c == c2: | ||
| continue | ||
| #위로 이동하는 경우만 검사 | ||
| if i == 0: | ||
| if c[1]==c2[1] and c[0]>c2[0]: | ||
| flag = False | ||
| break | ||
| elif i == 1: | ||
| if c[0]==c2[0] and c[1]<c2[1]: | ||
| flag = False | ||
| break | ||
| elif i == 2: | ||
| if c[1]==c2[1] and c[0]<c2[0]: | ||
| flag = False | ||
| break | ||
| elif i == 3: | ||
| if c[0]==c2[0] and c[1]>c2[1]: | ||
| flag = False | ||
| break | ||
|
|
||
|
|
||
| if flag: | ||
| can_go_dir.append(i) | ||
| # target_core 데이터 형태 (n좌표,m좌표,(이동가능한 방향 좌표)) | ||
| target_core.append((c,tuple(can_go_dir))) | ||
|
|
||
| dn = [-1,0,1,0] | ||
| dm = [0,1,0,-1] | ||
|
|
||
| # 현재 경로가 연결 가능한 경우 인지 판단하는 함수 | ||
| def check_path(n,m,dir): | ||
| nn = n + dn[dir] | ||
| nm = m + dm[dir] | ||
| length = 0 | ||
|
|
||
| while 0<=nn < N and 0 <= nm < N: | ||
| if cells[nn][nm] != 0 : | ||
| return 0 | ||
| length+=1 | ||
| nn += dn[dir] | ||
| nm += dm[dir] | ||
| return length | ||
|
|
||
| # value가 0일 때는 제거 2일 떄는 전선을 놓는 경우 | ||
| def set_path(n,m,dir,value): | ||
| nn = n + dn[dir] | ||
| nm = m + dm[dir] | ||
|
|
||
| while 0<= nn <N and 0 <= nm <N : | ||
| cells[nn][nm] = value | ||
|
|
||
| nn += dn[dir] | ||
| nm += dm[dir] | ||
|
|
||
| max_core = 0 | ||
| min_length = float('inf') | ||
|
|
||
| def dfs(idx, connected, total_length): | ||
| global max_core, min_length | ||
|
|
||
| if idx == len(target_core): | ||
| if connected>max_core: | ||
| max_core = connected | ||
| min_length = total_length | ||
|
|
||
| elif connected == max_core: | ||
| min_length = min(min_length,total_length) | ||
|
|
||
| return | ||
|
|
||
| (n,m),directions = target_core[idx] | ||
|
|
||
| for dir in directions: | ||
| length = check_path(n,m,dir) | ||
|
|
||
| if length == 0: | ||
| continue | ||
|
|
||
| set_path(n,m,dir,2) | ||
|
|
||
| dfs(idx+1,connected +1,total_length+length) | ||
|
|
||
| set_path(n,m,dir,0) | ||
| dfs(idx+1,connected,total_length) | ||
|
|
||
| dfs(0,0,0) | ||
|
|
||
| answer = min_length | ||
|
|
||
|
|
||
|
|
||
|
|
||
| print(f"#{t} {answer}") | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ### 🧠 회고 (선택) | ||
| - "가장자리 스킵 → 각 코어를 놓거나 포기 → 리프에서 (최대 연결, 최소 길이) 갱신"은 전형적인 배치형 백트래킹 골격이라 비슷한 유형(예: N개 대상 배치 최적화)에 그대로 재사용할 수 있다. | ||
| - `can_go_dir` 사전 가지치기는 `check_path`가 코어(`cells != 0`)도 어차피 걸러내므로 **기능상 중복**이긴 하다. 다만 분기 자체를 미리 줄여주는 최적화라 남겨둘 가치는 있다. 리뷰어에게: 이 사전 필터 없이 순수 `check_path`만으로도 제한 시간 안에 통과하는지 궁금하다. | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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
전체 시간 복잡도에 전처리 비용을 포함해 주세요.
현재
O(N · 5^K)는 DFS 탐색 비용만 설명합니다. 입력 스캔O(N²)과can_go_dir의 전체 코어 탐색 비용O(K·C)(C= 전체 코어 수)가 누락되어, 전체 복잡도는O(N² + K·C + N·5^K)로 적는 편이 정확합니다.🤖 Prompt for AI Agents