diff --git "a/studies/week-01/DevJunz/1767-\355\224\204\353\241\234\354\204\270\354\204\234\354\227\260\352\262\260\355\225\230\352\270\260.md" "b/studies/week-01/DevJunz/1767-\355\224\204\353\241\234\354\204\270\354\204\234\354\227\260\352\262\260\355\225\230\352\270\260.md" new file mode 100644 index 0000000..c424456 --- /dev/null +++ "b/studies/week-01/DevJunz/1767-\355\224\204\353\241\234\354\204\270\354\204\234\354\227\260\352\262\260\355\225\230\352\270\260.md" @@ -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 + + + 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 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`만으로도 제한 시간 안에 통과하는지 궁금하다.