구현하기 까다로운 구현 문제를 시뮬레이션이라고 한다
어려운 알고리즘을 쓰는 건 아니고 풀릴 것 같은데 막상 구현하려고 하면 꽤나 복잡해서 어떻게 해야 할지 모르겠는 문제들이다
푸는 것도 푸는 건데 문제가 길고 복잡해서 읽는 것도 힘들닿;
시간이 많지 않아서 문제 풀이 시간을 1~2시간으로 정해두고 그 안에 안 풀리면 답을 봤다
지금까지 풀어본 시뮬레이션 문제 유형
유형 1. 진짜 구현 (알고리즘 안쓰고 푸는 문제: 재귀, 반복문 등등)
유형 2. bfs+ 구현
유형 3. 백트래킹 + 구현 (풀이를 찾아보면 백트래킹 하는 부분을 dfs라고들 하는 것 같다)
유형 4. bfs + 백트래킹 + 구현
구현 - 배열 회전 시키기, 배열 한 방향으로 데이터 옮기기 등등
BOJ
몰라도 일단 풀어보라고 해서 풀었는데 틀렸다
너무 복잡한 방법을 구현하려다보니 구현 중 헷갈리는 부분이 한 두개가 아니었다
풀이를 보니 분명 내가 아는 알고리즘을 쓰고 있긴 했지만 코드양도 길고 복잡해서 이해하는 것도 꽤 오래걸렸다
조건
카메라의 종류 별로 한번에 감시할 수 있는 방향이 정해져 있으며, 카메라는 90도씩 회전할 수 있다
풀이
이런 사무실이 주어졌을 때 최소 사각지대를 구하기 위해선 각 카메라를 제일 많이 관찰할 수 있는 방향으로 돌린 후 0의 수를 세면 된다
하지만 컴퓨터는 바보라 한번에 계산할 수 없기 때문에 모든 경우를 돌아봐야 한다.
카메라들을 네방향으로 각각 돌려가며 사각지대를 구하고 그중에 가장 최소값을 찾도록 한다
경우의 수는 아래와 같다
2번 카메라 | 3번 카메라 |
상 | 상, 좌, 하, 우 |
좌 | 상, 좌, 하, 우 |
하 | 상, 좌, 하, 우 |
우 | 상, 좌, 하, 우 |
설계를 하자면
1. 입력 : 카메라의 위치와 종류를 리스트에 저장한다
2. 구현 : 카메라의 방향을 회전 시키며 관찰 가능한 영역을 방문 배열에 저장한다
3. 백트래킹 :리스트에 있는 모든 카메라가 방향을 정했다면
4. 구현: 방문 배열에서 방문하지 않은 공간을 확인하며 사각지대의 크기를 구한다
전체
import java.io.*
import java.util.*
import kotlin.math.min
private var n = 0
private var m = 0
private lateinit var office: Array<IntArray>
private lateinit var visited: Array<IntArray>
private val dx = arrayOf(1, 0, -1, 0) // 상 좌 하 우
private val dy = arrayOf(0, -1, 0, 1)
private val cctvList = mutableListOf<CCTV>()
private var answer = 0
private data class CCTV(val x: Int, val y: Int, val number: Int)
private fun isOOB(x: Int, y: Int) = x !in 0 until n || y !in 0 until m
// 사각지대 계산
private fun countBlind(): Int {
var blind = 0
for (i in 0 until n) {
for (j in 0 until m) {
if (visited[i][j] == 0) blind++
}
}
return blind
}
fun solve(idx: Int) {
// 모든 cctv의 방향을 정함
if (idx == cctvList.size) {
// 사각지대 계산
val result = countBlind()
answer = min(answer, result)
return
}
val start = cctvList[idx]
// 네방향 회전
for (dir in 0 until 4) {
rotate(start, dir, 1)
solve(idx+1)
// 다시 탐색할 수 있도록 visited 초기화
rotate(start, dir,-1)
}
}
// 카메라 방향 회전
private fun rotate(start: CCTV, dirIdx: Int, num: Int) {
val (x, y) = Pair(start.x, start.y)
when (start.number) {
// 한번에 한 영역 관찰 가능
1 -> {
check(x, y, dirIdx, num)
}
// 한번에 <-> 양방향 관찰 가능
2 -> {
check(x, y, dirIdx, num)
check(x, y, (dirIdx + 2) % 4, num)
}
// 한번에 두방향 관찰 가능 ㄴ 모양
3 -> {
check(x, y, dirIdx, num)
check(x, y, (dirIdx+1) % 4, num)
}
// 한번에 세방향 관찰 가능 : ㅗ 모양
4 -> {
check(x, y, dirIdx, num)
check(x, y, (dirIdx+1) % 4, num)
check(x, y, (dirIdx+2) % 4, num)
}
// 한번에 네방향 관찰 가능
5 -> {
check(x, y, dirIdx, num)
check(x, y, (dirIdx+1) % 4, num)
check(x, y, (dirIdx+2) % 4, num)
check(x, y, (dirIdx+3) % 4, num)
}
}
}
// 정해준 방향으로 전진하며 관찰 가능한 범위를 탐색
private fun check(x: Int, y: Int, dir: Int, num: Int) {
var nx = x
var ny = y
while (true) {
nx += dx[dir]
ny += dy[dir]
if (isOOB(nx, ny)) return // 범위 초과거나 벽이면 탐색 중지
if (office[nx][ny] == 6) return
// 관찰 가능한 영역. 추후 사각지대 계산을 위한 방문 표시
visited[nx][ny] += num
}
}
fun main() = with(BufferedReader(InputStreamReader(System.`in`))) {
var st = StringTokenizer(readLine())
n = st.nextToken().toInt()
m = st.nextToken().toInt()
office = Array(n) { IntArray(m) }
visited = Array(n) { IntArray(m) }
var zeroCnt = 0
for (i in 0 until n) {
st = StringTokenizer(readLine())
for (j in 0 until m) {
val cur = st.nextToken().toInt()
office[i][j] = cur
when (cur) {
0 -> zeroCnt++
in 1..5 -> {
cctvList.add(CCTV(i, j, cur))
visited[i][j] = cur
}
6 -> visited[i][j] = cur
}
}
}
answer = zeroCnt
// cctv가 한개 이상일 경우 bfs 진행
if (cctvList.size != 0) {
solve(0)
}
val bw = BufferedWriter(OutputStreamWriter(System.out))
bw.write("$answer")
bw.close()
}
쉽지않다ㅎ
입력
1. 스티커를 입력 받는다
구현
2. 노트북 전체를 돌며 스티커를 붙일 수 있는 칸을 찾는다
3. 스티커를 붙일 수 있는 칸을 발견하면 스티커를 붙인다
4. 스티커를 붙일 수 없다면 90도 회전 시켜서 다시 2를 진행한다
5. 스티커를 세번 회전(90 -> 180 -> 270) 시켜도 붙일 수 없다면 스티커를 버린다
6. 모든 스티커를 붙이고 난 후 스티커가 붙어있는 영역의 크기를 구한다
문제에서 스티커가 회전을 할 수 있는데 직사각형이기 때문에 배열의 크기를 넉넉하게 잡아야 한다
스티커를 회전 시키는 방법 (배열 회전)
0,0 | 0,1 | 0,2 |
1,0 | 1,1 | 1,2 |
- paper_old[0][0] -> paper_new[0][1]
- paper_old[0][1] -> paper_new[1][1]
- paper_old[0][2] -> paper_new[2][1]
- paper_old[1][0] -> paper_new[0][0]
- paper_old[1][1] -> paper_new[1][0]
- paper_old[1][2] -> paper_new[2][0]
규칙을 찾아보면 old(r,c) -> new(c, 2-1-r)이다 (2는 paper_old의 c size)
회전 구현
// 스티커 회전 시키기
private fun rotate() {
val tmp = Array(12) { IntArray(12) }
// 원본 복사
for (i in 0 until r) {
for (j in 0 until c) {
tmp[i][j] = paper[i][j]
}
}
// 회전
for (i in 0 until c) {
for (j in 0 until r) {
paper[i][j] = tmp[r-1-j][i]
}
}
// 스티커가 직사각형이기 때문에 r, c 값 교환
r = c.also { c = r }
}
노트에 스티커를 붙일 때 위치는 시작 위치 + 스티커 위치를 해주어야 한다
스티커는 (0,0)부터 시작하지만 노트에서 시작하는 위치가 달라지기 때문에 실제 위치를 구해주어야 한다
전체 코드
import java.io.*
import java.util.*
private lateinit var note: Array<IntArray>
private var n = 0 // 노트북 크기
private var m = 0 // 노트북 크기
private var k = 0 // 스티커 수
private var r = 0 // 스티커 크기
private var c = 0 // 스티커 크기
// 회전 시켜야 하기 때문에 크기 여유있게 잡기
private val paper = Array(12){ IntArray(12) }
fun main() = with(BufferedReader(InputStreamReader(System.`in`))) {
// 노트북 길이, 스티커 수 입력 받기
var st = StringTokenizer(readLine())
n = st.nextToken().toInt()
m = st.nextToken().toInt()
note = Array(n) { IntArray(m) }
k = st.nextToken().toInt()
while (k > 0) {
k--
// 스티커 크기
st = StringTokenizer(readLine())
r = st.nextToken().toInt()
c = st.nextToken().toInt()
// 스티커 입력받기
for (i in 0 until r) {
st = StringTokenizer(readLine())
for (j in 0 until c) {
paper[i][j] = st.nextToken().toInt()
}
}
// 노트북에 붙일 수 있는지 검사 (붙일 수 없으면 90 -> 180 -> 270 순으로 회전)
for (rotate in 0 until 4) {
var isAble = false
for (i in 0..n-r) {
if (isAble) break
for (j in 0..m-c) {
if (check(i, j)) {
// 스티커 붙이기
updateNote(i, j)
isAble = true
break
}
}
}
if (isAble) break
rotate()
}
}
var answer = 0
// 스티커가 붙어 있는 영역 검사
for (i in 0 until n) {
for (j in 0 until m) {
answer += note[i][j]
}
}
val bw = BufferedWriter(OutputStreamWriter(System.out))
bw.write("$answer")
bw.close()
}
private fun check(x: Int, y: Int): Boolean {
for (i in 0 until r) {
for (j in 0 until c) {
// 노트북 [i][j] 영역에 이미 스티커가 붙어 있음
if (note[i+x][j+y] == 1 && paper[i][j] == 1) return false
}
}
return true
}
// 스티커 회전 시키기
private fun rotate() {
val tmp = Array(12) { IntArray(12) }
// 원본 복사
for (i in 0 until r) {
for (j in 0 until c) {
tmp[i][j] = paper[i][j]
}
}
// 회전
for (i in 0 until c) {
for (j in 0 until r) {
paper[i][j] = tmp[r-1-j][i]
}
}
// 스티커가 직사각형이기 때문에 r, c 값 교환
r = c.also { c = r }
}
// 스티커 붙이기 x, y 노트에서의 위치
private fun updateNote(x: Int, y: Int) {
for (i in 0 until r) {
for (j in 0 until c) {
if(paper[i][j] == 1)
note[i+x][j+y] = 1
}
}
}
12100. 2048(Easy)
못풀었다 어렵다
풀면서 그래도 백트래킹 응용인것 같아 나름 쉽다고 생각하며 풀었는데 이렇게 될 줄 몰랐다
시간초과 지겨워ㅡㅡ
왜 시간 초과였냐면 우선 처음에는 치킨집들을 따로 저장하지 않고 map 전체를 탐색해서 시간초과가 났고
그다음에는 매번 치킨집 목록 전체를 탐색해서 시간초과
그래서 한번 조합에 사용된 치킨집은 제외하는 식으로 짰는데 계속 시간초과가 나와서 dora버릴뻔 했는데
// 1.폐업시키지 않을 치킨집을 M개 고른다
for (i in start until chickenList.size) {
selected[cnt] = i
solve(cnt+1, i+1)
}
이 부분에서 solve(cnt+1, start+1)로 값을 넘겨서 문제가 되었던 것이다 바보냐고
풀이
입력
1. 치킨집과 집을 각각 리스트에 저장한다
백트래킹
2. 폐업 시키지 않을 치킨집을 M개 고른다 -> 조합 구하기
3. 치킨 거리를 구한다 ((집 - 치킨집들 사이 최소 거리 구하기) x 집의 수만큼)
4. 치킨 거리 중 최소값을 구한다
import java.util.*
import java.io.*
import kotlin.collections.ArrayList
import kotlin.math.abs
import kotlin.math.min
private var n = 0
private var m = 0
private var map = Array(51) { IntArray(51) }
private val houseList = ArrayList<Location>()
private val chickenList = ArrayList<Location>()
private var answer = Int.MAX_VALUE
private var selected = IntArray(14)
private data class Location(val x: Int, val y: Int)
fun main() = with(BufferedReader(InputStreamReader(System.`in`))) {
var st = StringTokenizer(readLine())
n = st.nextToken().toInt()
m = st.nextToken().toInt()
map = Array(n) { IntArray(n) }
selected = IntArray(m)
repeat(n) { i ->
st = StringTokenizer(readLine())
repeat(n) { j ->
map[i][j] = st.nextToken().toInt()
if (map[i][j] == 1) houseList.add(Location(i, j))
if (map[i][j] == 2) chickenList.add(Location(i, j))
}
}
solve(0, 0)
val bw = BufferedWriter(OutputStreamWriter(System.out))
bw.write("$answer")
bw.close()
}
private fun solve(cnt: Int, start: Int) {
// 2. 폐업시키지 않을 치킨집 m개 고르기 완료
if (cnt == m) {
var result = 0
// 3. 치킨거리 구하기
for (i in 0 until houseList.size) {
var min = Int.MAX_VALUE
val h = houseList[i]
for (j in 0 until m) {
val chicken = chickenList[selected[j]]
val dist = abs(h.x - chicken.x) + abs(h.y - chicken.y)
min = min(dist, min)
}
result += min
}
// 최소 치킨 거리
answer = min(answer, result)
return
}
// 1.폐업시키지 않을 치킨집을 M개 고른다
for (i in start until chickenList.size) {
selected[cnt] = i
solve(cnt+1, i+1)
}
}
테스트 케이스랑 반례에서는 다 의도한대로 답이 나왔는데 실제로 돌려보면 틀렸습니다가 나왔다
아마 쓸데 없는 연산(배열 복사)를 해서 너무 느려서 그랬던 것 같다
풀이
1. 4개 이상 색이 일치하는 뿌요가 있는 지 탐색한다
2. 색이 일치하는 뿌요가 4개 이상 있다면 뿌요를 터뜨린다 (일치하는 뿌요를 찾는 과정에서 bfs 이용)
3. 1-2 과정을 뿌요 종류만큼 반복한다
4. 3까지 완료 후 터진 뿌요가 있다면 연쇄 수를 늘려준다
5. 남아있는 뿌요들을 아래로 내려준다
1~2는 bfs를 사용했다
그리고 탐색할 때 뿌요 수를 체크만 하는게 아니라 어딘가에 저장을 해두어야 한다
처음에 풀 때 뿌요 수를 카운팅 하고 4개 이상이면 visited 배열에 체크된 부분을 다 .으로 만드는 식으로 짰는데
......
..y...
..y...
.....y
.....y
....yy
이런 식으로 연속된 뿌요가 4개 이상 존재하면서 연속되지 않은 뿌요도 같이 있는 상황이 있을 수도 있기 때문에
4개 이상 연속되는 뿌요를 저장하고 이를 터뜨려주는 식으로 만들어야 한다
그리고 뿌요를 아래로 내려주는 부분은 열을 한 줄씩 탐색하는 방법으로 해결했다
1. 열을 아래에서 위로 탐색하며 뿌요를 모두 tmp 배열에 담는다 (index 11 ~0)
2. tmp에 있는 뿌요를 다시 map으로 옮겨준다
private fun fall() {
for (c in 0 until 6) {
val tmp = CharArray(12) { '.' }
var idx = 0
for (r in 11 downTo 0) {
if (map[r][c] == '.') continue
tmp[idx++] = map[r][c]
}
for (r in 11 downTo 0) {
map[r][c] = tmp[11-r]
}
}
}
전체 코드
import java.io.*
import java.util.*
private val map = Array(12) { CharArray(6) }
private val puyo = arrayOf('R', 'G', 'B', 'P', 'Y')
private var totalCnt = 0
private var isPop = false
fun main() = with(BufferedReader(InputStreamReader(System.`in`))) {
// input
repeat(12) { i ->
val s = readLine()
repeat(6) { j ->
map[i][j] = s[j]
}
}
solve()
val bw = BufferedWriter(OutputStreamWriter(System.out))
bw.write("$totalCnt")
bw.close()
}
private fun solve() {
do {
isPop = false
for (p in puyo) {
search(p)
}
if (isPop) totalCnt++
fall()
} while (isPop)
}
private data class Location(val x: Int, val y: Int)
private val dx = arrayOf(1, -1, 0, 0)
private val dy = arrayOf(0, 0, -1, 1)
private fun search(type: Char) {
val visited = Array(12) { BooleanArray(6) { false } }
val queue = ArrayDeque<Location>()
for (i in 0 until 12) {
for (j in 0 until 6) {
val puyoList = mutableListOf<Location>()
if (map[i][j] != type) continue
if (visited[i][j]) continue
// type에 일치하는 뿌요 발견. 탐색 시작
queue.add(Location(i, j))
visited[i][j] = true
puyoList.add(Location(i, j))
while (queue.isNotEmpty()) {
val cur = queue.remove()
for (dir in 0 until 4) {
val nx = cur.x + dx[dir]
val ny = cur.y + dy[dir]
if (isOOB(nx, ny) || visited[nx][ny]) continue // 범위 밖이거나
if (map[nx][ny] != type) continue // 같은 뿌요가 아닐 경우
queue.add(Location(nx, ny))
visited[nx][ny] = true
puyoList.add(Location(nx, ny))
}
}
// 연속하는 뿌요가 4개 이상일 경우
if (puyoList.size >= 4) {
isPop = true
// 뿌요 터짐
for (p in puyoList) {
map[p.x][p.y] = '.'
}
}
}
}
}
private fun isOOB(x: Int, y: Int) = x !in 0 until 12 || y !in 0 until 6
// 뿌요 떨어짐
private fun fall() {
for (c in 0 until 6) {
val tmp = CharArray(12) { '.' }
var idx = 0
for (r in 11 downTo 0) {
if (map[r][c] == '.') continue
tmp[idx++] = map[r][c]
}
for (r in 11 downTo 0) {
map[r][c] = tmp[11-r]
}
}
}
문제 제대로 읽고 예제도 돌려보고 코드 작성하기!
1. 회전 시키기로 한 톱니가 돌면 어떤 톱니에 어떻게 영향이 가는지 확인한다 (오른쪽, 왼쪽 나눠서 재귀)
2. 방향에 따라 톱니를 회전 시킨다
문제를 제대로 안읽고 푸니까 2-1 과정을 반대로 생각해서 풀다가 시간을 버렸고
다시 문제를 제대로 읽고 풀려니까 1이 어려웠다
재귀로 풀어야겠다는 생각은 했는데 방향을 나눠서 재귀한다는걸 떠올리지 못했다
풀다보면 나도 풀 수 있는 시뮬레이션 문제가 하나쯤은 생기겠지😩
import java.io.*
import java.util.*
private val gear = Array(4) { IntArray(8) }
private var k = 0
private var score = 0
fun main() = with(BufferedReader(InputStreamReader(System.`in`))) {
// input
repeat(4) { i ->
val s = readLine()
for (j in s.indices) {
gear[i][j] = s[j].digitToInt()
}
}
k = readLine().toInt()
var st: StringTokenizer
// 회전
while (k-- != 0) {
st = StringTokenizer(readLine())
val number = st.nextToken().toInt() - 1
val dir = st.nextToken().toInt()
simulation(number, dir)
}
// 점수 계산
score()
val bw = BufferedWriter(OutputStreamWriter(System.out))
bw.write("$score")
bw.close()
}
private fun simulation(r: Int, dir: Int) {
left(r, dir)
right(r, dir)
rotate(r, dir)
}
// r - 현재 위치 dir - r의 회전 방향
private fun left(r: Int, dir: Int) {
if (r-1 < 0) return // 왼쪽이 없음
if (gear[r][6] == gear[r-1][2]) return
// 다음 왼쪽 검사
left(r-1, -dir)
// r의 영향을 받아 r의 왼쪽 톱니가 -dir 받향으로 회전
rotate(r-1, -dir)
}
private fun right(r: Int, dir: Int) {
if (r+1 > 3) return
if (gear[r][2] == gear[r+1][6]) return
// 다음 오른쪽 검사
right(r+1, -dir)
// r의 영향을 받아 r의 오른쪽 톱니가 -dir 받향으로 회전
rotate(r+1, -dir)
}
private fun rotate(r: Int, dir: Int) {
// 시계 방향 회전
if (dir == 1) {
val tmp = gear[r][7]
for (i in 7 downTo 1) {
gear[r][i] = gear[r][i-1]
}
gear[r][0] = tmp
} else { // 반시계 방향 회전
val tmp = gear[r][0]
for (i in 0 until 7) {
gear[r][i] = gear[r][i+1]
}
gear[r][7] = tmp
}
}
private fun score() {
if (gear[0][0] == 1) score++
if (gear[1][0] == 1) score += 2
if (gear[2][0] == 1) score += 4
if (gear[3][0] == 1) score += 8
}
백트래킹이나 다른 알고리즘 사용할 필요 없이 문제 그대로 구현하면 된다
1. 주사위를 움직인다 (움직이기 전 목적지가 보드 밖으로 벗어나지 않는지 확인한다)
2. 보드판 바닥 면에 적힌 숫자를 확인하여 주사위 혹은 보드판의 숫자를 변경한다
3. 주사위 윗면에 적힌 숫자를 출력한다
일단 1번을 하기 위해서 주사위를 동,서,남,북 으로 이동시켰을 때 각각의 위치가 어떻게 변화하는지를 각각 함수로 구현한다
공간감각이 떨어져서 좀 헷갈렸지만 차근차근하면 할 수 있다
private fun moveE() { // 동
// 앞, 뒤 그대로
// 위, 앞, 아래, 뒤, 왼쪽, 오른쪽 -> 왼쪽, 앞, 오른쪽, 뒤, 아래, 위
val tmp = dice[0]
dice[0] = dice[4]
dice[4] = dice[2]
dice[2] = dice[5]
dice[5] = tmp
}
private fun moveW() { // 서
// 앞, 뒤 그대로
// 위, 앞, 아래, 뒤, 왼쪽, 오른쪽 -> 오른쪽, 앞, 왼쪽, 뒤, 위, 아래
val tmp = dice[4]
dice[4] = dice[0]
dice[0] = dice[5]
dice[5] = dice[2]
dice[2] = tmp
}
private fun moveN() { // 남
// 왼, 오 그대로
// 위,앞,아래,뒤 -> 뒤,위,앞, 아래
val tmp = dice[3]
for (i in 3 downTo 1) {
dice[i] = dice[i-1]
}
dice[0] = tmp
}
private fun moveS() { // 북
// 왼, 오 그대로
// 위,앞,아래,뒤 -> 앞, 아래, 뒤, 위
val tmp = dice[0]
for (i in 0..2) {
dice[i] = dice[i+1]
}
dice[3] = tmp
}
전체 구현
import java.util.*
import java.io.*
import java.lang.StringBuilder
private var n = 0
private var m = 0
private var x = 0
private var y = 0
private var k = 0
private lateinit var board: Array<IntArray>
private var dir = 0
private val dice = arrayOf(0, 0, 0, 0, 0, 0) // 위, 앞, 아래, 뒤, 왼쪽, 오른쪽
private val sb = StringBuilder()
fun main() = with(BufferedReader(InputStreamReader(System.`in`))) {
// input
var st = StringTokenizer(readLine())
n = st.nextToken().toInt()
m = st.nextToken().toInt()
x = st.nextToken().toInt()
y = st.nextToken().toInt()
k = st.nextToken().toInt()
board = Array(n) { IntArray(m) }
repeat(n) { i ->
st = StringTokenizer(readLine())
repeat(m) { j ->
board[i][j] = st.nextToken().toInt()
}
}
st = StringTokenizer(readLine())
while (k-- != 0) {
dir = st.nextToken().toInt()
when (dir) {
1 -> { // 동쪽
y += 1
if (isOOB(x, y)) {
y -= 1
continue
}
moveE()
}
2 -> { // 서쪽
y -= 1
if (isOOB(x, y)) {
y += 1
continue
}
moveW()
}
3 -> { // 북쪽
x -= 1
if (isOOB(x, y)) {
x += 1
continue
}
moveN()
}
4 -> { // 남쪽
x += 1
if (isOOB(x, y)) {
x -= 1
continue
}
moveS()
}
}
copy()
sb.append("${dice[0]}\n") // output
}
val bw = BufferedWriter(OutputStreamWriter(System.out))
bw.write(sb.toString())
bw.close()
}
private fun copy() {
// board에 쓰여 있는 수가 0이면
if (board[x][y] == 0) {
// 주사위 바닥에 있는 수가 복사
board[x][y] = dice[2]
} else { // 0이 아니면 board에 있는 수가 주사위로 복사
dice[2] = board[x][y]
board[x][y] = 0
}
}
private fun isOOB(x: Int, y: Int) = x !in 0 until n || y !in 0 until m
private fun moveE() { // 동
// 앞, 뒤 그대로
// 위, 앞, 아래, 뒤, 왼쪽, 오른쪽 -> 왼쪽, 앞, 오른쪽, 뒤, 아래, 위
val tmp = dice[0]
dice[0] = dice[4]
dice[4] = dice[2]
dice[2] = dice[5]
dice[5] = tmp
}
private fun moveW() { // 서
// 앞, 뒤 그대로
// 위, 앞, 아래, 뒤, 왼쪽, 오른쪽 -> 오른쪽, 앞, 왼쪽, 뒤, 위, 아래
val tmp = dice[4]
dice[4] = dice[0]
dice[0] = dice[5]
dice[5] = dice[2]
dice[2] = tmp
}
private fun moveN() { // 남
// 왼, 오 그대로
// 위,앞,아래,뒤 -> 뒤,위,앞, 아래
val tmp = dice[3]
for (i in 3 downTo 1) {
dice[i] = dice[i-1]
}
dice[0] = tmp
}
private fun moveS() { // 북
// 왼, 오 그대로
// 위,앞,아래,뒤 -> 앞, 아래, 뒤, 위
val tmp = dice[0]
for (i in 0..2) {
dice[i] = dice[i+1]
}
dice[3] = tmp
}
큐로 다리를 만든다
1 다리를 지나가려는 트럭의 무게 + 다리 위에 있는 총 무게가 최대 하중보다 작거나 같은지 확인한다
1-1. 작거나 같다면 다리 위에 트럭을 추가한다 (큐 맨 마지막에 트럭의 무게를 추가한다)
1-2. 더 크다면 트럭이 지나갈 수 없기 때문에 0을 추가한다
2. 트럭이 앞으로 전진한다(큐에서 맨 앞쪽에 있는 원소를 제거한다)
1~2를 큐가 빌 때까지 반복한다
(큐에 0을 넣어주는 이유는 시간초 측정을 위함)
import java.io.BufferedReader
import java.io.BufferedWriter
import java.io.InputStreamReader
import java.io.OutputStreamWriter
import java.util.*
import kotlin.collections.ArrayDeque
private var n = 0
private var w = 0 // 다리길이
private var l = 0 // 최대하중
private val trucks = ArrayDeque<Int>()
private val bridge = ArrayDeque<Int>()
private var time = 0
fun main() = with(BufferedReader(InputStreamReader(System.`in`))) {
// input
var st = StringTokenizer(readLine())
n = st.nextToken().toInt()
w = st.nextToken().toInt()
l = st.nextToken().toInt()
st = StringTokenizer(readLine())
repeat(n) {
trucks.addLast(st.nextToken().toInt())
}
repeat(w) {
bridge.addLast(0)
}
var totalW = 0
while (bridge.isNotEmpty()) {
time++
totalW -= bridge.removeFirst()
if (trucks.isNotEmpty()) {
if (trucks.first() + totalW <= l) {
totalW += trucks.first()
bridge.addLast(trucks.removeFirst())
} else {
bridge.addLast(0)
}
}
}
val bw = BufferedWriter(OutputStreamWriter(System.out))
bw.write("$time")
bw.close()
}
여태 배운걸 모두 사용하는 문제라 좋은 문제 같다는 생각이 든다
복습할 때 이 문제를 풀어보도록 해야겠다
1. 입력받기
3차원 배열을 선언하여 미로를 입력받는다
입구와 출구는 (0, 0, 0) (4,4,4)로 고정한다
2. 판 회전 시키기
판은 0 -> 90 -> 180 -> 270네방향으로 회전이 가능하다
판은 5개이기 때문에 각 판마다 회전 방향을 정해서 조합을 뽑아야 한다 (중복 조합)
예시: (1: 0, 2: 0, 3: 0, 4: 0, 5: 0), (1:0, 2: 90, 3: 0, 4: 0, 5: 0), (1: 0, 2: 180, 3: 0, 4: 0, 5: 0) ... (1: 270, 2: 270, 3: 270, 4: 270, 5: 270) 이런식으로 판을 회전시키는 모든 조합을 구한다
private fun solve(depth: Int) {
// 모든 판이 회전 방향 정하기 완료
if (depth == N) {
// 판 쌓기
stackFloor(0)
return
}
// 회전 방향 정하기 (0, 90, 180, 270)
for (i in 0 until 4) {
rotate(depth)
solve(depth + 1)
}
}
판 회전 시키기 (스티커 붙이기 문제 참고)
private fun rotate(h: Int) {
val tmp = Array(N) { IntArray(N) }
repeat(N) { r ->
repeat(N) { c ->
tmp[r][c] = maze[h][N-1-c][r]
}
}
System.arraycopy(tmp, 0, maze[h], 0, tmp.size)
}
3. 모든 판이 회전 방향을 정했다면 판을 쌓아야 하는데 이때도 조합을 구해야 한다 (중복 불가능)
예: (1, 2, 3, 4, 5), (1, 2, 3, 5, 4), (1, 2, 4, 3, 5) ... (5, 4, 3, 2, 1)
private val floor = IntArray(N)
private val isUsed = BooleanArray(N) { false }
private fun stackFloor(idx: Int) {
// 판 쌓는 순서 정하기 완료
if (idx == N) {
// 판 쌓기
val nMaze = Array(N) { Array(N) { IntArray(N) } }
for (i in 0 until N) {
val fIdx = floor[i]
System.arraycopy(maze[fIdx], 0, nMaze[i], 0, maze[fIdx].size)
}
// 미로 탐색 시작
bfs(nMaze)
return
}
// 판 쌓는 순서 조합 구하기 : 중복 불가
for (i in 0 until N) {
if (isUsed[i]) continue
isUsed[i] = true
floor[idx] = i
stackFloor(idx + 1)
isUsed[i] = false
}
}
4. 판 쌓기가 완료되었다면 미로 탐색을 시작한다 -> bfs 사용
만약 입구와 출구가 막혀있다면 탈출이 불가능 한 것으로 간주해야 하기 때문에 bfs 탐색 전 확인한다
최소 이동 횟수는 bfs 최단 거리를 구하는 것과 같기 때문에 visited에 저장되는 값을 탐색 회차마다 늘려나간다
private data class Location(val h: Int, val r: Int, val c: Int)
private val dx = arrayOf(1, -1, 0, 0, 0, 0) // 위, 아래, 오른쪽, 왼쪽, 앞, 뒤
private val dy = arrayOf(0, 0, 1, -1, 0, 0)
private val dz = arrayOf(0 ,0 ,0, 0, 1, -1)
// 미로 탐색
private fun bfs(maze: Array<Array<IntArray>>){
// 입구나 출구가 없을 경우
if (maze[0][0][0] == 0 || maze[4][4][4] == 0) return
val q = ArrayDeque<Location>()
val visited = Array(N) { Array(N) { IntArray(N) { -1 } } }
visited[0][0][0]++
q.add(Location(0, 0, 0))
while (q.isNotEmpty()) {
val (h, r, c) = q.remove()
val currentDist = visited[h][r][c]
if (h == 4 && r == 4 && c == 4) {
if (answer > currentDist) {
answer = currentDist
}
return
}
for (dir in 0 until 6) {
val nz = h + dz[dir]
val nx = r + dx[dir]
val ny = c + dy[dir]
if (nx !in 0 until N || ny !in 0 until N || nz !in 0 until N) continue
// 갈 수 없는 길, 이미 방문한 곳일 경우
if (maze[nz][nx][ny] == 0 || visited[nz][nx][ny] != -1) continue
visited[nz][nx][ny] = currentDist + 1
q.add(Location(nz, nx, ny))
}
}
}
bfs로 풀어야 하나 했는데 그럴 필요 없었다 쓰면 오히려 더 복잡해짐
0 -> 3, 3 -> 2, 2 -> 1, 1 -> 0 이걸 나는 수식이 떠오르지 않아 when으로 구현했는데 (i + 3) % 4로 표현하는 방법도 있다
import java.util.*
import java.io.*
private var N = 0
private var M = 0
private lateinit var room: Array<IntArray>
private var n = 0
private var m = 0
private var d = 0
private var answer = 0
fun main() = with(BufferedReader(InputStreamReader(System.`in`))) {
// input
var st = StringTokenizer(readLine())
N = st.nextToken().toInt()
M = st.nextToken().toInt()
room = Array(N) { IntArray(M) }
// 시작 위치
st = StringTokenizer(readLine())
n = st.nextToken().toInt()
m = st.nextToken().toInt()
d = st.nextToken().toInt()
// 방
repeat(N) { i->
st = StringTokenizer(readLine())
repeat(M) { j ->
room[i][j] = st.nextToken().toInt()
}
}
// simulation
clean()
// output
val bw = BufferedWriter(OutputStreamWriter(System.out))
bw.write("$answer")
bw.close()
}
private val dx = arrayOf(-1, 0, 1, 0) // 북, 동, 남, 서
private val dy = arrayOf(0, 1, 0, -1) // 북, 동, 남, 서
private fun clean() {
while (true) {
// 현 위치 청소
if (room[n][m] == 0) {
room[n][m] = -1
answer++
}
var isClean = false
// 네방향 청소
for (i in 0 until 4) {
d = (d+3) % 4
if (room[n + dx[d]][m + dy[d]] == 0) {
n += dx[d]
m += dy[d]
isClean = true
break
}
}
if (isClean) continue
// 네방향에서 청소를 한번도 못한 경우
// 후진했을 때 뒤가 벽이라면
if (room[n - dx[d]][m - dy[d]] == 1) break
// 후진 가능
n -= dx[d]
m -= dy[d]
}
}
3190. 뱀
우선 뱀의 머리 위치가 계속 바뀌고 꼬리 위치가 유지 / 삭제 될 수 있다는 점에서 큐나 덱을 떠올려야 한다
나는 덱으로 구현했다
1. 사과의 위치를 보드에 표시한다 (사과: 2, 뱀: 1, 길: 0)
1-1. 명령을 리스트에 담는다
2. 뱀의 시작 위치(1,1)를 덱에 넣어준 후 board에 뱀이 있는 곳을 표시한다
3. 탐색 시작하기 전, 뱀의 머리가 향할 부분이 범위를 초과하거나 자신의 몸이 있는 위치인지를 확인해준다 (초과하거나 몸과 닿으면 종료)
4. 머리 부분이 향하는 곳에 사과가 없다면 몸의 길이가 유지되어야 하기 때문에 덱에서 제일 마지막에 있는 요소를 제거한 후 board에 뱀의 꼬리가 있던 부분을 길로 바꿔준다 (board 값 : 1 -> 0 으로 변경)
5. 머리의 위치를 바꿔주고 덱 front에 머리 위치를 삽입한다
6. 시간초가 명령 리스트에 있는 값과 일치하면 방향을 바꿔준다
사과의 위치가 1,1 에서 시작하는데 처음에 그냥 0,0 으로 두고 했더니 초과 범위 설정할 때 헷갈려서 계속 IndexOutOfBounds 오류가 나왔다. 결국에는 아래와 같이 board 크기를 N보다 크게 잡아주고 시작을 1,1로 했다
0 | 1 | 2 | |
0 | x | x | x |
1 | x | 시작 | 길 |
2 | x | 길 | 길 |
(n이 2일 경우)
import java.util.*
import java.io.*
private val board = Array(105) { IntArray(105) } // 사과 2, 뱀: 1, 길: 0
private var n = 0
private var apple = 0
private val orderList = mutableListOf<Order>()
private data class Order(val time: Int, val dir: Char)
fun main() = with(BufferedReader(InputStreamReader(System.`in`))) {
n = readLine().toInt()
apple = readLine().toInt()
var st: StringTokenizer
repeat(apple) { i ->
st = StringTokenizer(readLine())
board[st.nextToken().toInt()][st.nextToken().toInt()] = 2
}
val orderCnt = readLine().toInt()
repeat(orderCnt) {
st = StringTokenizer(readLine())
orderList.add(Order(st.nextToken().toInt(), st.nextToken()[0]))
}
solve()
val bw = BufferedWriter(OutputStreamWriter(System.out))
bw.write("$time")
bw.close()
}
private var time = 0
private fun isOOB(x: Int, y:Int) = x < 1 || x > n || y < 1 || y > n
private data class Move(val x: Int, val y: Int)
private val dx = arrayOf(0, 1, 0, -1) // 우하좌상
private val dy = arrayOf(1, 0, -1, 0)
private fun solve() {
val snake = ArrayDeque<Move>()
var x = 1
var y = 1
var d = 0
var idx = 0
snake.addFirst(Move(x, y))
board[x][y] = 1
while (true) {
time++
// 머리가 도착할 곳
val nx = x + dx[d]
val ny = y + dy[d]
// 범위 초과
if (isOOB(nx, ny)) break
// 몸에 닿는지 검사
if (board[nx][ny] == 1) break
// 이동한 칸에 사과가 없을 경우
if (board[nx][ny] != 2) {
// 꼬리 자르기
val (tx, ty) = snake.removeLast()
board[tx][ty] = 0
}
// 머리 이동
board[nx][ny] = 1
snake.addFirst(Move(nx, ny))
x = nx
y = ny
// 방향 전환
if (idx < orderList.size) {
if (time == orderList[idx].time) {
d= if (orderList[idx++].dir == 'D') {
(d+1) % 4
} else (d+3) % 4
}
}
}
}
13460. 구슬 탈출 2
풀릴 줄 알았는데 안 풀리는 문제
처음에 백트래킹으로 풀려고 했는데 가지치기를 잘못해서 실패했다
그리고 다른 풀이들을 보니 4차원 배열을 사용한 방문체크 + bfs로 풀고 있었다
이런 풀이는 어떻게 떠올리는 건데
import java.io.*
import java.util.*
private var n = 0
private var m = 0
private lateinit var board: Array<CharArray>
private lateinit var dist: Array<Array<Array<IntArray>>>
private var iBall = Ball(0, 0, 0, 0)
data class Ball(val rx: Int, val ry: Int, val bx: Int, val by: Int)
fun main() = with(BufferedReader(InputStreamReader(System.`in`))) {
val st = StringTokenizer(readLine())
n = st.nextToken().toInt()
m = st.nextToken().toInt()
board = Array(n) { CharArray(m) }
dist = Array(n) { Array(m) { Array(n) { IntArray(m) { -1 } } } }
repeat(n) { i ->
val s = readLine()
repeat(m) { j ->
board[i][j] = s[j]
when (board[i][j]) {
'R' -> {
iBall = iBall.copy(rx = i, ry = j, bx = iBall.bx, by = iBall.by)
board[i][j] = '.'
}
'B' -> {
iBall = iBall.copy(rx = iBall.rx, ry = iBall.ry, bx = i, by = j)
board[i][j] = '.'
}
}
}
}
val answer = solve()
val bw = BufferedWriter(OutputStreamWriter(System.out))
bw.write("$answer")
bw.close()
}
private val dx = arrayOf(-1, 0, 1, 0) // 상우하좌
private val dy = arrayOf(0, 1, 0, -1)
private fun solve(): Int {
val q = ArrayDeque<Ball>()
q.add(iBall)
dist[iBall.rx][iBall.ry][iBall.bx][iBall.by] = 0
while (q.isNotEmpty()) {
// init
val (rx, ry, bx, by) = q.remove()
val cnt = dist[rx][ry][bx][by]
// 10번 넘음
if (cnt == 10) {
return -1
}
for (dir in 0 until 4) {
var nrx = rx
var nry = ry
var nbx = bx
var nby = by
// 가장자리에 '#'이 있기 때문에 oob 체크 x. #/O 앞에서 정지
// B 이동
while (board[nbx + dx[dir]][nby + dy[dir]] == '.') {
nbx += dx[dir]
nby += dy[dir]
}
// B 탈출. 실패하는 경로. 하지만 다른 방향으로 탐색할 때 성공할 수도 있기 때문에 탐색을 계속한다
if (board[nbx + dx[dir]][nby + dy[dir]] == 'O')
continue
// R 이동
while (board[nrx + dx[dir]][nry + dy[dir]] == '.') {
nrx += dx[dir]
nry += dy[dir]
}
// R 탈출. 성공
if (board[nrx + dx[dir]][nry + dy[dir]] == 'O') {
return cnt + 1
}
// R과 B가 겹쳐진 경우
if (nrx == nbx && nry == nby) {
when (dir) {
0 -> { // 위
// 더 큰 x가 뒤로감
if (rx > bx) nrx -= dx[dir] else nbx -= dx[dir]
}
1 -> { // 오른쪽
// 더 작은 y가 뒤로감
if (ry < by) nry -= dy[dir] else nby -= dy[dir]
}
2 -> { // 아래
// 더 작은 x가 뒤로감
if (rx < bx) nrx -= dx[dir] else nbx -= dx[dir]
}
3 -> { // 왼쪽
// 더 큰 y가 뒤로감
if (ry > by) nry -= dy[dir] else nby -= dy[dir]
}
}
}
// 이미 방문한적 있는 경로
if (dist[nrx][nry][nbx][nby] != -1) continue
dist[nrx][nry][nbx][nby] = cnt + 1
q.add(Ball(nrx, nry, nbx, nby))
}
}
return -1
}
14502 연구소
내가 푼 시뮬레이션 문제가 생겼다 ㅎㅎㅎ
이전에 풀어봤던 문제들과 비슷한 부분이 있어서 풀 수 있었다🥳
약간의 고난이 있었는데, 배열 복사할 때 값이 아니라 참조가 복사되어 이전 회차 바이러스가 확산된 위치가 다음 회차에도 반영되어 있었다.. (System.arrCopy 사용)
다른 사람 풀이에선 아래처럼 배열 복사를 하고 있었는데 다음에 사용해봐야겠다
private fun copyMap() {
for (i in copy.indices) {
for (j in copy[i].indices) {
copy[i][j] = map[i][j]
}
}
}
풀이
1. 백트래킹 : 벽을 3개 세운다
2. bfs: 바이러스가 퍼진다
3. 안전 영역의 크기를 구한다
백트래킹으로 벽을 3개 세우는 위치 조합 구하고, 3개를 모았다면 바이러스가 퍼진 상황을 만든 후 안전영역을 카운트 하면 된다
배열 복사에 문제가 있어서 한 공간에 벽을 세우고 바이러스가 퍼진 상황은 만들지 못했고
대신에 벽을 세운 공간, 바이러스가 퍼진 공간을 따로 만들어서
벽도 세워지지 않고 바이러스도 퍼지지 않은 공간을 안전영역으로 카운팅했다
import java.util.*
import java.io.*
import kotlin.math.max
private var n = 0
private var m = 0
private lateinit var map: Array<IntArray>
private lateinit var visited: Array<IntArray>
private val virusList = mutableListOf<Location>()
private val emptyList = mutableListOf<Location>()
private var answer = 0
private data class Location(val x: Int, val y: Int)
fun main() = with(BufferedReader(InputStreamReader(System.`in`))) {
var st = StringTokenizer(readLine())
n = st.nextToken().toInt()
m = st.nextToken().toInt()
map = Array(n) { IntArray(m) }
visited = Array(n) { IntArray(m) }
repeat(n) { i ->
st = StringTokenizer(readLine())
repeat(m) { j ->
map[i][j] = st.nextToken().toInt()
visited[i][j] = map[i][j]
if (map[i][j] == 2)
virusList.add(Location(i, j))
else if (map[i][j] == 0)
emptyList.add(Location(i, j))
}
}
solve(0, 0)
val bw = BufferedWriter(OutputStreamWriter(System.out))
bw.write("$answer")
bw.close()
}
// cnt: 벽의 수
private fun solve(idx: Int, start: Int) {
if (idx == 3) {
// 바이러스 퍼진 후 안전 영역 카운트
answer = max(answer, spreadV())
return
}
// 벽 세우는 조합 만들기
for (i in start until emptyList.size) {
val cur = emptyList[i]
// 이미 벽을 세운 곳
if (visited[cur.x][cur.y] != 0) continue
// 벽 세우기
visited[cur.x][cur.y] = 1
solve(idx+1, i+1)
visited[cur.x][cur.y] = 0
}
}
// 바이러스 확산 시뮬레이션
private fun spreadV(): Int {
val dx = arrayOf(-1, 0, 1, 0) // 상 우 하 좌
val dy = arrayOf(0, 1, 0, -1)
val q = ArrayDeque<Location>()
val vVisited = Array(n) { BooleanArray(m) { false } }
for (vLocation in virusList) {
q.add(vLocation)
vVisited[vLocation.x][vLocation.y] = true
while (q.isNotEmpty()) {
val (x, y) = q.remove()
for (dir in 0 until 4) {
val nx = x + dx[dir]
val ny = y + dy[dir]
if (nx !in 0 until n || ny !in 0 until m) continue
// 이미 방문했거나, 빈공간이 아닐 경우
if (vVisited[nx][ny] || visited[nx][ny] != 0) continue
vVisited[nx][ny] = true
q.add(Location(nx, ny))
}
}
}
// 안전 영역 확인
var cnt = 0
for (i in 0 until n) {
for (j in 0 until m) {
if (!vVisited[i][j] && visited[i][j] == 0) cnt++
}
}
return cnt
}
q14888 연산자 끼워넣기
풀 수 있을 줄 알았는데 시간복잡도 계산을 잘못해서 틀렸다
연산자 조합을 구하고 그다음에 연산을 했는데 이렇게 할 필요 없이 재귀로 계산한 값을 넘겨주면 된다
import java.io.*
import java.util.*
import kotlin.math.*
private var n = 0
private lateinit var numbers: IntArray
private val operator = IntArray(4) // 0: +, 1: -, 2: *, 3: /
private var max = -Int.MAX_VALUE
private var min = Int.MAX_VALUE
private fun input() = with(BufferedReader(InputStreamReader(System.`in`))){
n = readLine().toInt()
numbers = IntArray(n)
var st = StringTokenizer(readLine())
repeat(n) { i ->
numbers[i] = st.nextToken().toInt()
}
st = StringTokenizer(readLine())
repeat(4) {
operator[it] = st.nextToken().toInt()
}
}
private fun output() = with(BufferedWriter(OutputStreamWriter(System.out))){
write("$max\n$min")
close()
}
fun main() {
input()
solve(1, numbers[0])
output()
}
private fun solve(k: Int, ans: Int) {
if (k == n) {
max = max(ans, max)
min = min(ans, min)
return
}
for (i in 0 until 4) {
if(operator[i] > 0) {
operator[i]-- // 연산자 사용
when (i) {
0 -> solve(k+1, ans + numbers[k])
1 -> solve(k+1, ans - numbers[k])
2 -> solve(k+1, ans * numbers[k])
3 -> solve(k+1, ans / numbers[k])
}
operator[i]++
}
}
}
q14889 스타트와 링크
팀 빌딩 시 절반의 인원을 뽑으면 다른 팀도 정해지게 되어있다
때문에 인원의 절반만 뽑고 능력치 계산을 한다
1. bfs : 절반의 인원을 중복되지 않게 뽑는다 (조합) : size n짜리 방문체크용 배열에 방문을 표시한다
2. 구현 : n/2명의 인원을 뽑았다면 능력치 계산을 한다
import java.util.*
import java.io.*
import kotlin.math.abs
import kotlin.math.min
private var n = 0
private lateinit var arr: Array<IntArray>
private lateinit var used: BooleanArray
fun main() = with(BufferedReader(InputStreamReader(System.`in`))) {
n = readLine().toInt()
arr = Array(n) { IntArray(n) }
used = BooleanArray(n) { false }
var st: StringTokenizer
repeat(n) { i ->
st = StringTokenizer(readLine())
repeat(n) { j ->
arr[i][j] = st.nextToken().toInt()
}
}
buildTeam(0,0)
val bw = BufferedWriter(OutputStreamWriter(System.out))
bw.write("$min")
bw.close()
}
private var min = Int.MAX_VALUE
private fun buildTeam(depth: Int, s: Int) {
if (depth == n/2) {
min = min(calculate(), min)
return
}
for (i in s until n) {
if (used[i]) continue
used[i] = true
buildTeam(depth+1, i+1)
used[i] = false
}
}
private fun calculate(): Int {
var result = 0
for (i in 0 until n-1) {
for (j in i+1 until n) {
if (used[i] && used[j]) {
result += arr[i][j] + arr[j][i]
} else if (!used[i] && !used[j]) {
result -= arr[i][j] + arr[j][i]
}
}
}
return abs(result)
}
참고
'알고리즘&자료구조' 카테고리의 다른 글
DP: Dynamic Programming (0) | 2023.01.19 |
---|---|
kotlin 문자열 자르기 (0) | 2023.01.11 |
백트래킹 (0) | 2022.12.24 |
재귀 (0) | 2022.12.14 |
BFS 응용문제 풀기 (0) | 2022.12.06 |