├── .gitignore ├── README.md ├── advanced ├── 01-constructive.pdf ├── 01-constructive.typ ├── 02-shortest-path.pdf ├── 02-shortest-path.typ ├── 03-graph-dp.pdf ├── 03-graph-dp.typ ├── 04-advanced-dp.pdf ├── 04-advanced-dp.typ ├── 05-disjoint-set.pdf ├── 05-disjoint-set.typ ├── 06-advanced-number-theory.pdf └── 06-advanced-number-theory.typ ├── basic ├── 01-orientation.pdf ├── 01-orientation.typ ├── 02-number-theory.pdf ├── 02-number-theory.typ ├── 03-linear-data.pdf ├── 03-linear-data.typ ├── 04-search.pdf ├── 04-search.typ ├── 05-associated-data.pdf ├── 05-associated-data.typ ├── 06-optimization.pdf ├── 06-optimization.typ ├── 07-graph.pdf └── 07-graph.typ ├── slide.typ └── solution ├── 1929 ├── Main.java ├── main.cpp ├── main.js └── main.py ├── 2040 ├── README.md ├── main.cpp ├── main.py └── main.rs ├── 4375 ├── Main.java ├── README.md ├── main.cpp ├── main.js └── main.py ├── 9655 ├── main.cpp ├── main.py └── main.rs ├── 9711 ├── Main.java ├── main.cpp ├── main.js └── main.py ├── 11660 ├── main.cpp ├── main.py └── main.rs ├── 14889 ├── main.cpp ├── main.py └── main.rs ├── 14928 ├── Main.java ├── main.cpp ├── main.js └── main.py ├── 15552 ├── Main.java ├── README.md ├── main.cpp ├── main.js └── main.py ├── 15649 ├── main.cpp ├── main.py └── main.rs ├── 24039 ├── Main.java ├── main.cpp ├── main.js └── main.py ├── 27065 ├── Main.java ├── main.cpp ├── main.js └── main.py ├── 27965 ├── Main.java ├── main.cpp └── main.py ├── 28138 ├── Main.java ├── main.cpp └── main.py └── 30242 ├── main.cpp ├── main.py └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /*.pdf 2 | main 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PoolC 알고리즘 세미나 2 | 3 | ## [기초](https://github.com/kiwiyou/algorithm-lecture/tree/main/basic) 4 | 5 | ### [01. 오리엔테이션](https://github.com/kiwiyou/algorithm-lecture/blob/main/basic/01-orientation.pdf) 6 | - [15552 빠른 A+B](https://acmicpc.net/problem/15552) 7 | 8 | ### [02. 정수론](https://github.com/kiwiyou/algorithm-lecture/blob/main/basic/02-number-theory.pdf) 9 | - [9711 피보나치](https://acmicpc.net/problem/9711) 10 | - [4375 1](https://acmicpc.net/problem/4375) 11 | - [14928 큰 수 (BIG)](https://acmicpc.net/problem/14928) 12 | - [27965 N결수](https://acmicpc.net/problem/27965) 13 | - [24039 2021은 무엇이 특별할까?](https://acmicpc.net/problem/24039) 14 | - [27065 2022년이 아름다웠던 이유](https://acmicpc.net/problem/27065) 15 | - [28138 재밌는 나머지 연산](https://acmicpc.net/problem/28138) 16 | - [1929 소수 구하기](https://acmicpc.net/problem/1929) 17 | - [2421 저금통](https://acmicpc.net/problem/2421) 18 | - [16563 어려운 소인수분해](https://acmicpc.net/problem/16563) 19 | 20 | ### [03. 선형 자료구조](https://github.com/kiwiyou/algorithm-lecture/blob/main/basic/03-linear-data.pdf) 21 | - [27522 카트라이더: 드리프트](https://acmicpc.net/problem/27522) 22 | - [29723 브실이의 입시전략](https://acmicpc.net/problem/29723) 23 | - [30684 모르고리즘 회장 정하기](https://acmicpc.net/problem/30684) 24 | - [30047 함수 문자열](https://acmicpc.net/problem/30047) 25 | - [17952 과제는 끝나지 않아!](https://acmicpc.net/problem/17952) 26 | - [11899 괄호 끼워넣기](https://acmicpc.net/problem/11899) 27 | - [23827 수열 (Easy)](https://acmicpc.net/problem/23827) 28 | - [21921 블로그](https://acmicpc.net/problem/21921) 29 | - [25947 선물할인](https://acmicpc.net/problem/25947) 30 | - [28427 Tricknology](https://acmicpc.net/problem/28427) 31 | 32 | ## [중급](https://github.com/kiwiyou/algorithm-lecture/tree/main/advanced) 33 | 34 | ### [01. 해답의 구성](https://github.com/kiwiyou/algorithm-lecture/blob/main/advanced/01-constructive.pdf) 35 | - [15649 N과 M (1)](https://acmicpc.net/problem/15649) 36 | - [30242 N-Queen (Easy)](https://acmicpc.net/problem/30242) 37 | - [14889 스타트와 링크](https://acmicpc.net/problem/14889) 38 | - [1799 비숍](https://acmicpc.net/problem/1799) 39 | - [9655 돌 게임](https://acmicpc.net/problem/9655) 40 | - [2040 수 게임](https://acmicpc.net/problem/2040) 41 | 42 | ### [02. 그래프의 최단거리](https://github.com/kiwiyou/algorithm-lecture/blob/main/advanced/02-shortest-path.pdf) 43 | - [11660 구간 합 구하기 5](https://acmicpc.net/problem/11660) 44 | - [25682 체스판 다시 칠하기 2](https://acmicpc.net/problem/25682) 45 | - [14846 직사각형과 쿼리](https://acmicpc.net/problem/14846) 46 | - [1613 역사](https://acmicpc.net/problem/1613) 47 | - [11562 백양로 브레이크](https://acmicpc.net/problem/11562) 48 | - [1865 웜홀](https://acmicpc.net/problem/1865) 49 | - [14284 간선 이어가기 2](https://acmicpc.net/problem/14284) 50 | - [25636 소방차](https://acmicpc.net/problem/25636) 51 | - [28131 K-지폐](https://acmicpc.net/problem/28131) -------------------------------------------------------------------------------- /advanced/01-constructive.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kiwiyou/algorithm-lecture/a176b7ef09d0c1592a0979bc7e4ffad3177160ce/advanced/01-constructive.pdf -------------------------------------------------------------------------------- /advanced/01-constructive.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/cetz:0.1.2" 2 | #import "@preview/algorithmic:0.1.0" 3 | #import "../slide.typ" 4 | #show: slide.style 5 | #show link: slide.link 6 | #show footnote.entry: slide.footnote 7 | 8 | #let algorithm(..args) = text(font: ("linux libertine", "Pretendard"), size: 17pt)[#algorithmic.algorithm(..args)] 9 | #let func(body) = text(font: ("linux libertine", "Pretendard"))[#smallcaps[#body]] 10 | 11 | #align(horizon + center)[ 12 | = 알고리즘 중급 세미나 13 | 01: 해답의 구성 14 | 15 | #text(size: 0.8em)[ 16 | 연세대학교 전우제#super[kiwiyou] \ 17 | 2023.11.24.r1 18 | ] 19 | ] 20 | 21 | #slide.slide([백트래킹])[ 22 | - 특정한 조건을 만족하는 배치를 전부 탐색할 때 쓰는 기법 23 | 24 | - 한 수를 놓아 보고 안 되면 퇴각 25 | 26 | - 일반적으로 다음에 놓을 수와 지금까지 놓은 수에 대한 정보를 매개변수로 하는 재귀 함수 27 | 28 | #pagebreak() 29 | 30 | - 돈이 $M$원 있다. $N$개의 품목의 가격은 $C_i > 0$이고 만족도는 $P_i$이다. 최대 만족도는? 31 | 32 | #algorithm({ 33 | import algorithmic: * 34 | Function("Max-Profit", args: ([$M$], [$N$], [$C$], [$P$])) 35 | Assign[$m$][0 #Ic[최대 만족도]] 36 | For( 37 | cond: [$i <- 0$ *until* $N$], 38 | If( 39 | cond: [$M >= C_i$], 40 | Assign([$p$], [$P_i +$ #CallI("Max-Profit", [$M - C_i$, $N$, $C$, $P$])]), 41 | Assign([$m$], $max(m, p)$) 42 | ) 43 | ) 44 | Return[$m$] 45 | }) 46 | 47 | - $C_i = 1$이면 $N$개 품목마다 #func[Max-Profit($M - 1$)]을 호출: $cal(O)(N^M)$ 48 | 49 | #pagebreak() 50 | 51 | - 최적화: $1 -> 2 -> 1$과 $1 -> 1 -> 2$는 같다! 52 | 53 | #algorithm({ 54 | import algorithmic: * 55 | Function("Max-Profit", args: ([$M$], [$N$], [$C$], [$P$], [$i$])) 56 | If( 57 | cond: [$i = N$], 58 | Return[$0$] 59 | ) 60 | Assign[$m$][0] 61 | Assign($c$)[0 #Ic[$i$번 품목 구매 개수]] 62 | While( 63 | cond: [$c times C_i <= M$], 64 | Assign([$p$], [$c times P_i +$ #CallI("Max-Profit", [$M - c times C_i$, $N$, $C$, $P$, $i + 1$])]), 65 | Assign([$m$], $max(m, p)$), 66 | Assign([$c$], $c + 1$), 67 | ) 68 | Return[$m$] 69 | }) 70 | 71 | - $attach(Pi, bl: N, br: M)$가지 $i$의 배치 중 *순서를 강제*\했으므로, $cal(O)(attach(H, bl: N, br: M))$ 72 | 73 | #pagebreak() 74 | 75 | - 각 배치를 출력해 보자! 76 | #algorithm({ 77 | import algorithmic: * 78 | Function("Max-Profit", args: ([$M$], [$N$], [$C$], [$P$], [$i$], [$R$])) 79 | If( 80 | cond: [$i = N$], 81 | Call("Print", [$R$]), 82 | Return[$0$], 83 | ) 84 | State[...] 85 | While( 86 | cond: [$c times C_i <= M$], 87 | [*append* $i$ to $R$], 88 | Assign([$p$], [$c times P_i +$ #CallI("Max-Profit", [$M - c times C_i$, $N$, $C$, $P$, $i + 1$])]), 89 | [*remove* $i$ from $R$ #Ic[퇴각]], 90 | [...] 91 | ) 92 | Return[$m$] 93 | }) 94 | ] 95 | 96 | #slide.slide([게임 이론])[ 97 | - 주어진 게임에서 서로가 최선을 다할 때 승리 / 패배하는 사람은 누구이며, 필승 전략은 무엇인가? 98 | 99 | - 여기서 다루는 게임은 *조합론적 게임* 100 | - 2인이 번갈아 진행하며 101 | - 모든 정보가 공개되어 있고 102 | - 비기는 경우가 없는 게임 103 | 104 | - 두 플레이어가 같은 상황에 놓였다면 같은 선택을 함 105 | 106 | - 서로가 최선을 다한다는 조건을 최대한 이용해서 풀기 107 | 108 | #pagebreak() 109 | 110 | - 더 이상 진행할 수 없는 사람이 111 | - 패배: 일반 게임 112 | - 승리: 미제르 게임 113 | 114 | - 일반 게임의 경우, 더 이상 진행할 수 없는 상황을 만드는 것이 목표 115 | 116 | - 다음 수가 이기는 수인지 지는 수인지 확정할 수 있다면 이전 수도 확정 117 | 118 | - 더 이상 진행할 수 없는 상황으로부터 다이나믹 프로그래밍을 통해 각 상황에서의 승패를 확정 가능 119 | 120 | #pagebreak() 121 | 122 | - 배스킨라빈스 31 123 | - 31을 외치면 *패배* 124 | - 30을 외치고 31에서 상대가 패배 -> *승리* 가능 125 | - 30, 31을 외치고 패배 가능 126 | - ... 127 | 128 | - 다음 상황이 모두 (상대의) *패배*\라면 *승리*\하는 수 129 | 130 | - 다음 상황이 하나라도 (상대의) *승리*\라면 *패배*\하는 수 131 | 132 | - *승리*\하는 수가 하나라도 있으면 *승리* 133 | 134 | #pagebreak() 135 | 136 | #algorithm({ 137 | import algorithmic: * 138 | Function("Is-First-Win", args: ([$n$], )) 139 | If( 140 | cond: [$n >= 31$], 141 | Return[*false*], 142 | ) 143 | If( 144 | cond: [#CallI("Is-First-Win")[$n+1$] *or* #CallI("Is-First-Win")[$n+2$] *or* #CallI("Is-First-Win")[$n+3$]], 145 | Return[*false*], 146 | ) 147 | Return[*true*] 148 | }) 149 | 150 | #algorithm({ 151 | import algorithmic: * 152 | Function("Is-First-Win-Iterative", args: ([$n$], )) 153 | Assign[table][${}$] 154 | Assign[table$[31]$, table$[32]$, table$[33]$][*false*] 155 | For( 156 | cond: [$i = 30$ *downto* $n$], 157 | Assign[table$[i]$][*not*(table$[i+1]$ *or* table$[i+2]$ *or* table$[i+3]$)] 158 | ) 159 | Return[table[$n$]] 160 | }) 161 | ] 162 | 163 | #slide.slide([과제])[ 164 | - #slide.problem("15649", "N과 M (1)") 165 | 166 | - #slide.problem("30242", "N-Queen (Easy)") 167 | 168 | - #slide.problem("14889", "스타트와 링크") 169 | 170 | - #slide.problem("1799", "비숍") 171 | 172 | - #slide.problem("9655", "돌 게임") 173 | 174 | - #slide.problem("2040", "수 게임") 175 | ] -------------------------------------------------------------------------------- /advanced/02-shortest-path.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kiwiyou/algorithm-lecture/a176b7ef09d0c1592a0979bc7e4ffad3177160ce/advanced/02-shortest-path.pdf -------------------------------------------------------------------------------- /advanced/02-shortest-path.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/cetz:0.1.2" 2 | #import "@preview/algorithmic:0.1.0" 3 | #import "../slide.typ" 4 | #show: slide.style 5 | #show link: slide.link 6 | #show footnote.entry: slide.footnote 7 | 8 | #let algorithm(..args) = text( 9 | font: ("linux libertine", "Pretendard"), 10 | size: 17pt, 11 | )[#algorithmic.algorithm(..args)] 12 | #let func(body) = text(font: ("linux libertine", "Pretendard"))[#smallcaps[#body]] 13 | 14 | #align(horizon + center)[ 15 | = 알고리즘 중급 세미나 16 | 02: 그래프의 최단거리 17 | 18 | #text(size: 0.8em)[ 19 | 연세대학교 전우제#super[kiwiyou] \ 20 | 2023.12.05.r1 21 | ] 22 | ] 23 | 24 | #slide.slide( 25 | [누적 합], 26 | )[ 27 | - 구간의 합을 합의 차로 구하는 테크닉 28 | 29 | - $S_i = a_1 + a_2 + dots.c + a_i $일 때, $a_i + a_(i+1) + dots.c + a_j = S_j - S_(i-1)$ 30 | 31 | - $a_i$가 변하지 않을 때 구간의 합을 $cal(O)(1)$에 구함 32 | ] 33 | 34 | #slide.slide( 35 | [2차원 누적 합], 36 | )[ 37 | - 직사각형의 합을 구하는 테크닉 38 | 39 | #[ 40 | #show math.equation: set text(size: 22pt) 41 | $ 42 | S_(r, c) = sum mat( 43 | a_(1, 1), a_(1, 2), ..., a_(1, c); 44 | a_(2, 1), a_(2, 2), ..., a_(2, c); 45 | dots.v, dots.v, dots.down, dots.v; 46 | a_(r, 1), a_(r, 2), ..., a_(r, c); 47 | ) 48 | $ 49 | 50 | $ sum mat( 51 | a_(t, l), a_(t, l+1), ..., a_(t, r); 52 | a_(t+1, l), a_(t+1, l+1), ..., a_(t+1, r); 53 | dots.v, dots.v, dots.down, dots.v; 54 | a_(b, l), a_(b, l+1), ..., a_(b, r); 55 | ) = S_(b, r) - S_(t - 1, r) - S_(b, l - 1) + S_(t - 1, l - 1) $ 56 | ] 57 | 58 | #pagebreak() 59 | 60 | - 직사각형을 4분할한 뒤 생각 61 | 62 | #align(center + horizon)[ 63 | #show math.equation: set text(size: 20pt) 64 | #cetz.canvas({ 65 | import cetz.draw: * 66 | let size = 6 67 | 68 | line((0, -.4), (size, -.4)) 69 | line((-.4, 0), (-.4, size)) 70 | 71 | content((0, -.7), $0$, anchor: "top") 72 | content((size/2, -.7), $l$, anchor: "top") 73 | content((size, -.7), $r$, anchor: "top") 74 | content((-.7, size), $0$, anchor: "top-right") 75 | content((-.7, size/2), $t$, anchor: "right") 76 | content((-.7, 0), $b$, anchor: "right") 77 | 78 | rect((0, 0, 0), (size, size, 0), fill: rgb(100%, 0, 0, 25%)) 79 | rect((0, 0, 1), (size/2, size, 1), fill: rgb(0, 100%, 0, 25%)) 80 | rect((0, size/2, 2), (size, size, 2), fill: rgb(0, 0, 100%, 25%)) 81 | rect((0, size/2, 3), (size/2, size, 3), fill: rgb(0, 0, 0, 25%)) 82 | 83 | set-style(line: (stroke: (dash: "dashed"))) 84 | line((0, 0, 0), (0, 0, 1)) 85 | line((size/2, 0, 0), (size/2, 0, 1)) 86 | line((size/2, size/2, 0), (size/2, size/2, 3)) 87 | line((size, size/2, 0), (size, size/2, 2)) 88 | line((0, size/2, 0), (0, size/2, 3)) 89 | line((0, size, 0), (0, size, 3)) 90 | line((size/2, size, 0), (size/2, size, 3)) 91 | line((size, size, 0), (size, size, 2)) 92 | 93 | content((size*3/4, size/4), $S_(b, r)$, anchor: "center") 94 | content((size/4+.5, size/4+.4), $S_(b, l - 1)$, anchor: "center") 95 | content((size*3/4+1, size*3/4+1), $S_(t - 1, r)$, anchor: "center") 96 | content((size/4+1.5, size*3/4+1.5), $S_(t - 1, l - 1)$, anchor: "center") 97 | }) 98 | ] 99 | 100 | #pagebreak() 101 | 102 | - $S_(r, c)$ 구하는 법 103 | 104 | #align(center + horizon)[ 105 | #cetz.canvas({ 106 | import cetz.draw: * 107 | let size = 8 108 | let count = 5 109 | let step = size / count 110 | grid((-size/2 - .5, 0), (rel: (size, size)), step: step, name: "right") 111 | set-style( 112 | line: ( 113 | stroke: ( 114 | paint: red, 115 | thickness: 4pt, 116 | ), 117 | mark: ( 118 | fill: red, 119 | end: ">", 120 | size: 0.3, 121 | angle: 75deg, 122 | ), 123 | ), 124 | ) 125 | let i = 0 126 | while i < 5 { 127 | line((rel: (step/2, i * step + step/2), to: "right.bottom-left"), (rel: (-step/2, i * step + step/2), to: "right.bottom-right")) 128 | i += 1 129 | } 130 | 131 | grid((size/2 + .5, 0), (rel: (size, size)), step: step, name: "down") 132 | set-style( 133 | line: ( 134 | stroke: ( 135 | paint: blue, 136 | ), 137 | mark: ( 138 | fill: blue, 139 | ), 140 | ), 141 | ) 142 | let i = 0 143 | while i < 5 { 144 | line((rel: (i * step + step/2, -step/2), to: "down.top-left"), (rel: (i * step + step/2, step/2), to: "down.bottom-left")) 145 | i += 1 146 | } 147 | }) 148 | ] 149 | ] 150 | 151 | #slide.slide[과제][ 152 | - #slide.problem("11660", "구간 합 구하기 5") 153 | 154 | - #slide.problem("25682", "체스판 다시 칠하기 2") 155 | 156 | - #slide.problem("14846", "직사각형과 쿼리") 157 | ] 158 | 159 | #slide.slide[플로이드-워셜][ 160 | - 모든 두 정점 간 최단 경로 (APSP) 161 | 162 | - 도달성(추이 폐포)을 모두 구할 수 있음 163 | 164 | - 양수, 음수 가중치 모두 가능 165 | 166 | - 음수 사이클의 존재를 판단 가능 167 | 168 | #pagebreak() 169 | 170 | - 인접행렬 기반 171 | 172 | - 매번 각 정점이 양쪽으로 잇는 경로를 탐색 후 최단거리 갱신 (Relaxation) 173 | 174 | - 정점 $V$개, 정점과 연결된 양쪽 점을 찾는 데 $cal(O)(V^2)$ 175 | 176 | - $cal(O)(V^3)$ 177 | 178 | - 음수 사이클이 있다면 $u -> u$ 최단거리가 갱신 179 | 180 | - 오버플로에 주의 181 | 182 | #pagebreak() 183 | 184 | #algorithm({ 185 | import algorithmic: * 186 | Function("Floyd-Warshall", args: ($A$, ), { 187 | For( 188 | cond: [$"via"$ *in* $V$], { 189 | For( 190 | cond: [$u$ *in* $V$], { 191 | For( 192 | cond: [$v$ *in* $V$], 193 | If( 194 | cond: $A[u][v] > A[u]["via"] + A["via"][v]$, 195 | Assign[$A[u][v]$][$A[u]["via"] + A["via"][v]$] 196 | ) 197 | ) 198 | } 199 | ) 200 | For( 201 | cond: [$u$ *in* $V$], { 202 | If( 203 | cond: $A[u][u] < 0$, 204 | State[*report* negative cycle] 205 | ) 206 | } 207 | ) 208 | } 209 | ) 210 | }) 211 | }) 212 | 213 | - $A[u][v]$는 $u -> v$ 간선의 가중치 또는 $infinity$ 214 | 215 | #pagebreak() 216 | 217 | #algorithm({ 218 | import algorithmic: * 219 | Function("Transitive-Closure", args: ($A$, ), { 220 | For( 221 | cond: [$"via"$ *in* $V$], { 222 | For( 223 | cond: [$u$ *in* $V$], { 224 | For( 225 | cond: [$v$ *in* $V$], 226 | If( 227 | cond: [$A[u]["via"]$ *and* $A["via"][v]$], 228 | Assign[$A[u][v]$][*true*] 229 | ) 230 | ) 231 | } 232 | ) 233 | } 234 | ) 235 | }) 236 | }) 237 | 238 | - $A[u][v]$는 $u -> v$ 간선이 있으면 `true`, 없으면 `false` 239 | ] 240 | 241 | #slide.slide[과제][ 242 | - #slide.problem("1613", "역사") 243 | 244 | - #slide.problem("11562", "백양로 브레이크") 245 | ] 246 | 247 | #slide.slide[벨만-포드][ 248 | - 출발점 $s$에서 시작해서 모든 정점으로 끝나는 최단 경로 (SSSP) 249 | 250 | - 양수, 음수 가중치 모두 가능 251 | 252 | - 음수 사이클의 존재를 판단 가능 253 | 254 | #pagebreak() 255 | 256 | - $s$를 제외한 모든 정점까지의 거리 $D[u]$를 무한대로 놓고 시작 257 | 258 | - $|V|-1$번, 모든 간선을 이용해 Relax 259 | 260 | - 모든 최단경로는 $|V|-1$개의 간선으로 이루어짐 261 | 262 | - 음수 사이클이 있다면 $|V|$번째에 Relax 263 | 264 | - $|V|-1$번 모든 간선을 확인하므로 총 $cal(O)(V E)$ 265 | 266 | #pagebreak() 267 | 268 | #algorithm({ 269 | import algorithmic: * 270 | Function("Bellman-Ford", args: ($V$, $E$, $s$), { 271 | Assign[$D[v]$][*inf* for all $v in V$] 272 | Assign[$D[s]$][$0$] 273 | For( 274 | cond: [$|V| - 1$ times], { 275 | For( 276 | cond: [$(u, v, w)$ *in* $E$], 277 | If( 278 | cond: [$D[v] > D[u] + w$], 279 | Assign[$D[v]$][$D[u] + w$] 280 | ) 281 | ) 282 | } 283 | ) 284 | For( 285 | cond: [$(u, v, w)$ *in* $E$], 286 | If( 287 | cond: [$D[v] > D[u] + w$], 288 | State[*report* negative cycle] 289 | ) 290 | ) 291 | Return[$D$] 292 | }) 293 | }) 294 | ] 295 | 296 | #slide.slide[과제][ 297 | - #slide.problem("1865", "웜홀") 298 | ] 299 | 300 | #slide.slide[데이크스트라][ 301 | - 출발점 $s$에서 시작해서 모든 정점으로 끝나는 최단 경로 (SSSP) 302 | 303 | - $0$ 이상의 가중치에서만 사용 가능 304 | 305 | - $D[u]$가 최단거리라면 더 Relax되지 않음 306 | 307 | - $s -> v -> w$가 최단거리라면 $s -> v$ 또한 최단거리 308 | 309 | #pagebreak() 310 | 311 | - $s$를 제외한 모든 정점까지의 거리 $D[u]$를 무한대로 놓고 시작 312 | 313 | - 집합 $Q$: 아직 최단거리가 결정되지 않은 정점 314 | - 처음에 $Q = V$ 315 | 316 | - $Q$에서 $D[u]$가 가장 작은 정점 $u$를 뽑음 317 | 318 | - $u$와 연결된 각 정점 $v$를 Relax 319 | 320 | - $Q$에서 $u$를 찾는 데 총 $cal(O)(V^2)$, Relax에 쓰이는 간선 확인 총 $cal(O)(E)$ 321 | 322 | - $cal(O)(V^2+E)$? 323 | 324 | #pagebreak() 325 | 326 | - 이진 힙 (#sym.in 우선순위 큐) 327 | - 삽입에 $cal(O)(log N)$ 328 | - 최소 원소 확인에 $cal(O)(1)$ 329 | - 최소 원소 삭제에 $cal(O)(log N)$ 330 | 331 | - $cal(O)(E)$번의 Relax마다 힙에 삽입하므로 $cal(O)(E log E)$ 332 | 333 | - 그만큼 삭제가 일어나므로 $cal(O)(E log E)$ 334 | 335 | #pagebreak() 336 | 337 | - 이론적인 최소 시간복잡도 $cal(O)(E + V log V)$ (구현이 복잡하여 느림) 338 | 339 | - 피보나치 힙 340 | - 삽입에 $cal(O)(log N)$ 341 | - 최소 원소 삭제에 $cal(O)(log N)$ 342 | - 우선순위 변경에 $cal(O)(1)$ 343 | 344 | - $cal(O)(E)$번의 Relax마다 우선순위를 변경하므로 $cal(O)(E)$ 345 | 346 | - 힙에는 처음에 $cal(O)(V)$개의 원소가 들어가고 $cal(O)(V)$번 삭제가 일어나므로 $cal(O)(V log V)$ 347 | 348 | #pagebreak() 349 | 350 | #algorithm({ 351 | import algorithmic: * 352 | Function("Dijkstra", args: ($V$, $E$, $s$), { 353 | Assign[$D[v]$][*inf* for all $v in V$] 354 | Assign[$D[s]$][$0$] 355 | Assign[$Q$][${ (s, D[s]) }$ #Ic[Binary Heap]] 356 | While( 357 | cond: [$Q$ is not empty], { 358 | State[*remove* $(u, d)$ from $Q$ with minimum $d$] 359 | If( 360 | cond: $d > D[u]$, 361 | State[*continue*] 362 | ) 363 | For( 364 | cond: [$(u, v, w)$ in $E$], { 365 | If( 366 | cond: $D[v] > D[u] + w$, { 367 | Assign[$D[v]$][$D[u] + w$] 368 | State[*insert* $(v, D[v])$ into $Q$] 369 | } 370 | ) 371 | } 372 | ) 373 | } 374 | ) 375 | Return[$D$] 376 | }) 377 | }) 378 | - $d > D[u]$ 체크가 빠지면 $cal(O)(V^2 + E log E)$! 379 | ] 380 | 381 | #slide.slide[과제][ 382 | - #slide.problem("14284", "간선 이어가기 2") 383 | 384 | - #slide.problem("25636", "소방차") 385 | 386 | - #slide.problem("28131", "K-지폐") 387 | ] -------------------------------------------------------------------------------- /advanced/03-graph-dp.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kiwiyou/algorithm-lecture/a176b7ef09d0c1592a0979bc7e4ffad3177160ce/advanced/03-graph-dp.pdf -------------------------------------------------------------------------------- /advanced/03-graph-dp.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/cetz:0.1.2" 2 | #import "@preview/algorithmic:0.1.0" 3 | #import "../slide.typ" 4 | #show: slide.style 5 | #show link: slide.link 6 | #show footnote.entry: slide.footnote 7 | 8 | #let algorithm(..args) = text( 9 | font: ("linux libertine", "Pretendard"), 10 | size: 17pt, 11 | )[#algorithmic.algorithm(..args)] 12 | #let func(body) = text(font: ("linux libertine", "Pretendard"))[#smallcaps[#body]] 13 | 14 | #align(horizon + center)[ 15 | = 알고리즘 중급 세미나 16 | 03: 그래프에서의 다이나믹 프로그래밍 17 | 18 | #text(size: 0.8em)[ 19 | 연세대학교 전우제#super[kiwiyou] \ 20 | 2023.12.08.r1 21 | ] 22 | ] 23 | 24 | #slide.slide[방향 비순환 그래프][ 25 | - 방향이 있고#super[Directed], 순환이 없는#super[Acyclic] 그래프 26 | 27 | - 순서 관계를 일반화한 것? 28 | 29 | #align(center + horizon)[ 30 | #text(size: 20pt)[ 31 | #cetz.canvas({ 32 | import cetz.draw: * 33 | 34 | let r = 0.75cm 35 | set-style( 36 | content: ( 37 | frame: "circle", 38 | padding: 0.2, 39 | ), 40 | circle: ( 41 | radius: r 42 | ), 43 | line: ( 44 | mark: ( 45 | end: ">", 46 | size: 0.2, 47 | fill: black, 48 | ) 49 | ), 50 | ) 51 | let connect(a, b) = line((a + ".center", r, b + ".center"), (b + ".center", r, a + ".center")) 52 | 53 | group({ 54 | content((0, 0), [A], name: "A") 55 | content((3, 0), [B], name: "B") 56 | content((6, 0), [C], name: "C") 57 | content((9, 0), [D], name: "D") 58 | content((0, -2), [E], name: "E") 59 | content((9, -2), [F], name: "F") 60 | connect("A", "B") 61 | connect("B", "C") 62 | connect("C", "D") 63 | connect("E", "B") 64 | connect("C", "F") 65 | connect("B", "F") 66 | }, name: "O") 67 | 68 | content((rel: (0, -0.5), to: "O.bottom"), [DAG의 예], frame: none) 69 | 70 | group({ 71 | set-origin((12, 0)) 72 | content((0, 0), [A], name: "A") 73 | content((3, 0), [B], name: "B") 74 | content((6, 0), [C], name: "C") 75 | content((9, 0), [D], name: "D") 76 | content((0, -2), [E], name: "E") 77 | content((9, -2), [F], name: "F") 78 | connect("A", "B") 79 | connect("B", "C") 80 | connect("C", "D") 81 | connect("E", "B") 82 | connect("B", "F") 83 | connect("F", "E") 84 | let mid = ("E.center", 0.5, "F.center") 85 | set-style( 86 | line: ( 87 | mark: ( 88 | end: none 89 | ), 90 | stroke: ( 91 | paint: red, 92 | thickness: 0.1cm, 93 | ), 94 | ), 95 | ) 96 | line((rel: (-0.2, -0.2), to: mid), (rel: (0.2, 0.2), to: mid)) 97 | line((rel: (-0.2, 0.2), to: mid), (rel: (0.2, -0.2), to: mid)) 98 | }, name: "X") 99 | 100 | content((rel: (0, -0.5), to: "X.bottom"), [DAG가 아닌 예], frame: none) 101 | }) 102 | ] 103 | ] 104 | ] 105 | 106 | #slide.slide[위상 정렬][ 107 | - 정점을 순서대로 나열하는 방법 108 | 109 | - 순서가 정의되지 않은 두 정점의 경우 어떤 순서라도 가능 110 | 111 | #align(center + horizon)[ 112 | #text(size: 20pt)[ 113 | #cetz.canvas({ 114 | import cetz.draw: * 115 | 116 | let r = 0.75cm 117 | set-style( 118 | content: ( 119 | frame: "circle", 120 | padding: 0.2, 121 | ), 122 | circle: ( 123 | radius: r 124 | ), 125 | line: ( 126 | mark: ( 127 | end: ">", 128 | size: 0.2, 129 | fill: black, 130 | ) 131 | ), 132 | ) 133 | let connect(a, b) = line((a + ".center", r, b + ".center"), (b + ".center", r, a + ".center")) 134 | group({ 135 | content((0, 0), [A], name: "A") 136 | content((3, 0), [B], name: "B") 137 | content((6, 0), [C], name: "C") 138 | content((9, 0), [D], name: "D") 139 | content((0, -2), [E], name: "E") 140 | content((9, -2), [F], name: "F") 141 | connect("A", "B") 142 | connect("B", "C") 143 | connect("C", "D") 144 | connect("E", "B") 145 | connect("C", "F") 146 | connect("B", "F") 147 | }, name: "g") 148 | group(anchor: "top-right", { 149 | set-origin((rel: (0, -2), to: "g.bottom")) 150 | content((-6, 0), [A], name: "A") 151 | content((-4, 0), [E], name: "E") 152 | content((-2, 0), [B], name: "B") 153 | content((0, 0), [C], name: "C") 154 | content((2, 0), [D], name: "D") 155 | content((4, 0), [F], name: "F") 156 | connect("A", "E") 157 | connect("E", "B") 158 | connect("B", "C") 159 | connect("C", "D") 160 | connect("D", "F") 161 | }, name: "s1") 162 | group(anchor: "top-left", { 163 | set-origin((rel: (0, -2), to: "g.bottom")) 164 | content((-4, 0), [A], name: "A") 165 | content((-2, 0), [B], name: "B") 166 | content((0, 0), [C], name: "C") 167 | content((2, 0), [E], name: "E") 168 | content((4, 0), [F], name: "F") 169 | content((6, 0), [D], name: "D") 170 | connect("A", "B") 171 | connect("B", "C") 172 | connect("C", "E") 173 | connect("E", "F") 174 | connect("F", "D") 175 | }, name: "s2") 176 | 177 | set-style( 178 | line: ( 179 | mark: ( 180 | end: ">" 181 | ) 182 | ) 183 | ) 184 | 185 | line((rel: (-5, -0.5), to: "g.bottom"), (rel: (0, 0.5), to: "s1.top")) 186 | line((rel: (5, -0.5), to: "g.bottom"), (rel: (0, 0.5), to: "s2.top")) 187 | }) 188 | ] 189 | ] 190 | 191 | #pagebreak() 192 | 193 | - 진입 차수#super([indegree]): 정점을 가리키는 화살표의 개수 194 | 195 | - 진입 차수가 0인 정점을 아무거나 방문 196 | 197 | - 정점을 방문할 때마다 그 정점에서 출발하는 화살표를 모두 제거 198 | 199 | - 큐와 스택 모두 이용 가능 200 | 201 | #pagebreak() 202 | 203 | #algorithm({ 204 | import algorithmic: * 205 | 206 | Function("Topological-Sort", args: ($V$, $E$), { 207 | Assign[$"indegree"[u]$][$0$] 208 | For( 209 | cond: [$(u, v)$ *in* $E$], 210 | Assign[$"indegree"[v]$][$"indegree"[v] + 1$] 211 | ) 212 | Assign[$Q$][${}$] 213 | For( 214 | cond: [$u$ *in* $V$], 215 | If( 216 | cond: $"indegree"[u] = 0$, 217 | State[*add* $u$ to $Q$] 218 | ) 219 | ) 220 | Assign[$"order"$][${}$] 221 | While( 222 | cond: [$Q$ is not empty], 223 | { 224 | State[*pop* $u$ from $Q$] 225 | State[*append* $u$ to $"order"$] 226 | For( 227 | cond: [$(u, v)$ *in* $E$], 228 | { 229 | Assign[$"indegree"[v]$][$"indegree"[v] - 1$] 230 | If( 231 | cond: $"indegree"[v] = 0$, 232 | State[*add* $u$ to $Q$] 233 | ) 234 | } 235 | ) 236 | } 237 | ) 238 | Return[$"order"$] 239 | }) 240 | }) 241 | ] 242 | 243 | #slide.slide[DAG에서의 다이나믹 프로그래밍][ 244 | - 위상 정렬 후 bottom-up으로 접근하면 1차원 다이나믹 프로그래밍과 동일 245 | 246 | - 간선을 역방향으로 놓은 후 DFS 247 | - 동일 정점을 여러 번 방문하지 않도록 주의 248 | 249 | - 간선이 모두 부모 방향인 트리는 DAG 250 | ] 251 | 252 | #slide.slide[과제][ 253 | - #slide.problem("25168", "게으른 아리를 위한 접종 계획") 254 | 255 | - #slide.problem("24526", "전화 돌리기") 256 | 257 | - #slide.problem("26159", "트리와 수열") 258 | ] -------------------------------------------------------------------------------- /advanced/04-advanced-dp.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kiwiyou/algorithm-lecture/a176b7ef09d0c1592a0979bc7e4ffad3177160ce/advanced/04-advanced-dp.pdf -------------------------------------------------------------------------------- /advanced/04-advanced-dp.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/cetz:0.1.2" 2 | #import "@preview/algorithmic:0.1.0" 3 | #import "../slide.typ" 4 | #show: slide.style 5 | #show link: slide.link 6 | #show footnote.entry: slide.footnote 7 | 8 | #let algorithm(..args) = text( 9 | font: ("linux libertine", "Pretendard"), 10 | size: 17pt, 11 | )[#algorithmic.algorithm(..args)] 12 | #let func(body) = text(font: ("linux libertine", "Pretendard"))[#smallcaps[#body]] 13 | 14 | #align(horizon + center)[ 15 | = 알고리즘 중급 세미나 16 | 04: 고급 다이나믹 프로그래밍 17 | 18 | #text(size: 0.8em)[ 19 | 연세대학교 전우제#super[kiwiyou] \ 20 | 2023.01.04.r1 21 | ] 22 | ] 23 | 24 | #slide.slide[배낭 문제][ 25 | - $N$개의 물건에 무게 $w_i$와 가치 $v_i$가 있을 때, 무게 합이 $W$를 넘지 않도록 물건을 몇 개 골라 가치 합을 최대화 26 | 27 | - 여기서는 $w_i$가 모두 정수인 경우만 고려 28 | 29 | - *입력 크기*\에 대한 다항 시간에 푸는 알고리즘은 발견되지 않음 30 | 31 | - 탐욕적인 접근이 통하기 어려워 보이므로 DP로 접근 32 | 33 | #pagebreak() 34 | 35 | - $f(i, w) =$ $i$번째 물건까지 고려했을 때, 정확히 무게 $w$를 사용하여 얻을 수 있는 최대 가치 36 | 37 | - $f(i + 1, w) = max(f(i, w), f(i, w - w_(i + 1)) + v_i)$ 38 | 39 | - 시간 $cal(O)(N W)$, 공간 $cal(O)(N W)$ 40 | 41 | - 배열을 왼쪽부터 덮어씌우면서 공간 $cal(O)(W)$에 가능 42 | 43 | #pagebreak() 44 | 45 | - $N$가지의 물건에 무게 $w_i$, 가치 $v_i$, 개수 $c_i$가 있을 때, 무게 합이 $W$를 넘지 않도록 물건을 몇 개 골라 가치 합을 최대화 46 | 47 | - $C = sum_(i=1)^N c_i$개의 물건이 하나씩 있다고 생각하고 풀기 48 | 49 | - $O(C W)$ 50 | 51 | #pagebreak() 52 | 53 | - $N$가지 물건에 무게 $w_i$와 가치 $v_i$가 있을 때, 무게 합이 $W$를 넘지 않도록 물건을 몇 가지 골라 원하는 개수만큼 선택해 가치 합을 최대화 54 | 55 | - $f(i + 1, w) = max( & \ 56 | & f(i, w), \ 57 | & f(i, w - w_(i + 1)) + v_i, \ 58 | & f(i + 1, w + w_(i + 1)) + v_i \ 59 | )&$ 60 | 61 | - $cal(O)(N W)$ 62 | 63 | #pagebreak() 64 | 65 | - 그 외 최적화 기법은 https://infossm.github.io/blog/2023/03/18/Knapsack/ 참고 66 | ] 67 | 68 | #slide.slide[과제][ 69 | - #slide.problem("17845", "수강 과목") 70 | 71 | - #slide.problem("23257", "비트코인은 신이고 나는 무적이다") 72 | 73 | - #slide.problem("27163", "벚꽃 내리는 시대에 결투를") 74 | ] 75 | 76 | #slide.slide[LIS][ 77 | - 부분 수열#super([Subsequence]): 수열 $A$에서 몇 개의 원소를 지워 만든 수열 78 | 79 | - 가장 긴 증가하는 부분 수열#super([Longest Increasing Subsequence]) 80 | 81 | - 마찬가지로 탐욕법 적용이 어려움 82 | 83 | #pagebreak() 84 | 85 | - $f(i)$: $i$번째 원소를 끝으로 하는 가장 긴 증가하는 부분 수열의 길이 86 | 87 | - $ f(i) = max_(j 0$, { 44 | If(cond: $b "bitand" 1 = 1$, { 45 | Assign[$r$][$r times a$] 46 | }) 47 | Assign[$a$][$a times a$] 48 | Assign[$b$][$"rshift"(b, 1)$] 49 | }) 50 | Return[$r$] 51 | }) 52 | }) 53 | 54 | #pagebreak() 55 | 56 | - 행렬곱을 이용한 선형 점화식의 계산 57 | 58 | $ a_(n+2) = 3a_(n+1) + 2a_(n) + 1 $ 59 | $ mat(3, 2, 1; 1, 0, 0; 0, 0, 1) mat(a_(n+1); a_(n); 1) = mat(a_(n+2); a_(n+1); 1) $ 60 | $ mat(3, 2, 1; 1, 0, 0; 0, 0, 1)^n mat(a_(2); a_(1); 1) = mat(a_(n+2); a_(n+1); 1) $ 61 | 62 | #pagebreak() 63 | 64 | - 행렬곱에 $cal(O)(k^3)$ 시간이 걸리므로 $cal(O)(k^3 log n)$ 65 | 66 | - 슈트라센 알고리즘: 행렬곱은 $cal(O)(k^2.8)$ 정도에 계산 가능 67 | 68 | - 키타마사법: $cal(O)(k log k log n)$ 69 | 70 | #pagebreak() 71 | 72 | - Functional Graph: 진출차수가 1인 그래프 73 | 74 | - 어떤 정점 $v$에서 그래프를 따라 $k$번 이동한 위치를 구하시오. 75 | 76 | - $cal(O)(N log k)$ 77 | ] 78 | 79 | #slide.slide[모듈러 곱셈 역원][ 80 | - 연산 $circle.tiny$의 항등원 $e$: $a circle.tiny e = e circle.tiny a = a$ 81 | 82 | - $a$의 역원 $a^(-1)$: $a circle.tiny a^(-1) = a^(-1) circle.tiny a = e$ 83 | 84 | - $a$의 법 $N$에 대한 곱셈 역원 $a^(-1)$: $a times a^(-1) equiv 1 space (mod N)$ 85 | 86 | - $exists x in bb(Z): a times a^(-1) + N times x = 1$ 87 | 88 | #pagebreak() 89 | 90 | - 페르마의 소정리: $a^(p-1) equiv a space (mod p)$ 91 | 92 | - $a^(p-2) times a = a^(p-1) equiv 1 space (mod p)$ 93 | 94 | - $a^(-1) equiv a^(p-2) space (mod p)$ 95 | 96 | #pagebreak() 97 | 98 | - 확장 유클리드 호제법 99 | 100 | - $a x + b y = gcd(a, b)$를 만족하는 두 정수 $x$, $y$ 101 | 102 | $ 103 | a times 1 + b times 0 = a space.en& a times 0 + b times 1 = b \ 104 | a times 0 + b times 1 = b space.en& a times 1 + b times (-q) = c && space dots.c space (a = b q + c) \ 105 | a times 1 + b times (-q) = c space.en& a times (-1) + b times (1 + q') = d && space dots.c space (b = c q' + d) \ 106 | dots.v \ 107 | a x + b y = gcd(a, b) space.en& a x' + b y' = 0 108 | $ 109 | 110 | - 유클리드 호제법과 같은 $cal(O)(log max(a, b))$ 111 | ] -------------------------------------------------------------------------------- /basic/01-orientation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kiwiyou/algorithm-lecture/a176b7ef09d0c1592a0979bc7e4ffad3177160ce/basic/01-orientation.pdf -------------------------------------------------------------------------------- /basic/01-orientation.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/cetz:0.1.2" 2 | #import "@preview/algorithmic:0.1.0" 3 | #import "../slide.typ" 4 | #show: slide.style 5 | #show link: slide.link 6 | #show footnote.entry: slide.footnote 7 | 8 | #let algorithm(..args) = text(font: ("linux libertine", "Pretendard") ,size: 17pt)[#algorithmic.algorithm(..args)] 9 | 10 | #align(horizon + center)[ 11 | = 알고리즘 기초 세미나 12 | OT: 알고리즘 문제를 푸는 방법 13 | 14 | #text(size: 0.8em)[ 15 | 연세대학교 전우제#super[kiwiyou] \ 16 | 2023.11.15.r1 17 | ] 18 | ] 19 | 20 | #slide.slide([알고리즘 문제의 의도])[ 21 | - 시험자가 얼마나 원하는 기능을 잘 구현할 수 있는지 시험 22 | 23 | #align(horizon + center)[ 24 | #cetz.canvas({ 25 | import cetz.draw: * 26 | set-style( 27 | stroke: (thickness: 2pt), 28 | mark: (size: 0.5, angle: 80deg), 29 | ) 30 | content((0, 0), align(center)[기능 \ = 여러분의 코드], name: "code", padding: 1, frame: "rect") 31 | content((rel: (-3, 0), to: "code.left"), [입력], anchor: "right", name: "input") 32 | content((rel: (3, 0), to: "code.right"), [출력], anchor: "left", name: "output") 33 | line((rel: (0.5, 0), to: "input.right"), (rel: (-0.5, 0), to: "code.left"), mark: (end: ">")) 34 | line((rel: (0.5, 0), to: "code.right"), (rel: (-0.5, 0), to: "output.left"), mark: (end: ">")) 35 | }) 36 | ] 37 | #pagebreak() 38 | - "잘" 구현한다는 것은? 39 | 1. 마감 기한 안에 코드를 짤 수 있어야 함 40 | 41 | 2. 기능이 맡은 일을 정확하게 수행해야 함 42 | 43 | 3. 기능이 적당한 시간 안에 돌아가야 함 44 | 45 | #align(horizon + center)[ 46 | #cetz.canvas({ 47 | import cetz.draw: * 48 | set-style( 49 | stroke: (thickness: 2pt), 50 | mark: (size: 0.5, angle: 80deg), 51 | ) 52 | content((0, 0), [맡은 일], name: "task", anchor: "right", padding: 0.5) 53 | content((4, 1), [알고리즘을 응용하는 사고력 문제], anchor: "left", name: "think", padding: 0.5) 54 | content((4, -1), [알고리즘을 사용하는 실 기능 구현 문제], anchor: "left", name: "impl", padding: 0.5) 55 | line("task.right", "think.left", mark: (end: ">")) 56 | line("task.right", "impl.left", mark: (end: ">")) 57 | content(("task.right", .5, "think.left"), text(size: 0.8em)[대회], angle: "think.left", padding: 0.2, frame: "rect", fill: white, stroke: none) 58 | content(("task.right", .5, "impl.left"), text(size: 0.8em)[코테], angle: "impl.left", padding: 0.2, frame: "rect", fill: white, stroke: none) 59 | }) 60 | ] 61 | 62 | #pagebreak() 63 | 64 | - 구현력은 문제를 많이 푸는 경험으로 터득 65 | - #link("https://acmicpc.net/")[백준 온라인 저지] 66 | - #link("https://programmers.co.kr/")[프로그래머스] 67 | 68 | - 출제되는 문제의 유형을 파악하고 유형별 문제 해결 방법을 숙지 69 | ] 70 | 71 | #slide.slide([알고리즘 문제의 접근 방식])[ 72 | - 문제 상황을 알기 쉬운 형태로 정리 73 | 74 | -> 수식화, 알고 있는 문제로 변형 75 | 76 | - 문제에서 어느 정도로 효율적인 풀이를 원하는지 파악 77 | 78 | -> 시간복잡도 분석 79 | 80 | - 풀이가 비효율적이라면 어느 부분을 개선할 수 있는지 파악 81 | 82 | -> 필요없는 계산을 최대한 줄이기 83 | ] 84 | 85 | #slide.slide([시간복잡도 분석])[ 86 | - 현대 상용 컴퓨터는 1초에 수억#super[10#super[8]] 번의 단위 연산#super[unit operation]이 가능#footnote()[인터프리트되는 언어의 경우 단위 연산이 없는 것과 마찬가지이므로 이보다 훨씬 적은 수의 연산을 수행할 수 있다.] 87 | 88 | - 코드가 조금만 복잡해져도 단위 연산의 횟수를 정확하게 세는 것이 어려움 89 | 90 | - 입력 크기에 따라 단위 연산의 횟수가 얼마나 빠르게 증가하는지만 계산 91 | 92 | #pagebreak() 93 | 94 | #algorithm({ 95 | import algorithmic: * 96 | Function("Max-Sum-Pair", args: ([$A$], )) 97 | Assign[$m$][0] 98 | For( 99 | cond: [$i <- 0$ *until* $"len"(A)$], 100 | For( 101 | cond: [$j <- 0$ *until* $i$], 102 | Assign[$m$][$max(m, A[i] + A[j])$] 103 | ) 104 | ) 105 | Return[$m$] 106 | }) 107 | 108 | - 덧셈과 $max$ 연산은 모두 합쳐 $N^2 + N$번 일어나지만 $O(N^2)$으로 표기 109 | 110 | - $N = 5000$이라면 $N^2 = 2.5 times 10^7$이므로 $1$초 시간제한 내에 실행될 것 111 | 112 | - $N = 10^5$이라면 $N^2 = 10^10$이므로 $1$초 시간제한 내에 실행되지 않을 것 113 | 114 | - 정확한 계산이 아니므로 드물게 시간이 안 맞을 수 있음 115 | ] 116 | 117 | #slide.slide([빠른 입출력])[ 118 | - 백준 온라인 저지의 경우 미리 준비된 입력 파일들을 프로그램에 전달하고 프로그램의 출력을 출력 파일들과 비교 119 | 120 | - 콘솔 입출력 기능을 사용하면 되지만, 내부 동작 방식으로 인해 문제가 생길 수 있음 121 | ] 122 | 123 | #slide.slide([과제])[ 124 | - #slide.problem("15552", "빠른 A+B") 125 | ] -------------------------------------------------------------------------------- /basic/02-number-theory.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kiwiyou/algorithm-lecture/a176b7ef09d0c1592a0979bc7e4ffad3177160ce/basic/02-number-theory.pdf -------------------------------------------------------------------------------- /basic/02-number-theory.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/cetz:0.1.2" 2 | #import "@preview/algorithmic:0.1.0" 3 | #import "../slide.typ" 4 | #show: slide.style 5 | #show link: slide.link 6 | #show footnote.entry: slide.footnote 7 | 8 | #let algorithm(..args) = text(font: ("linux libertine", "Pretendard"), size: 17pt)[#algorithmic.algorithm(..args)] 9 | #let func(body) = text(font: ("linux libertine", "Pretendard"))[#smallcaps[#body]] 10 | 11 | #align(horizon + center)[ 12 | = 알고리즘 기초 세미나 13 | 02: 정수론 14 | 15 | #text(size: 0.8em)[ 16 | 연세대학교 전우제#super[kiwiyou] \ 17 | 2023.11.25.r3 18 | ] 19 | ] 20 | 21 | #slide.slide[합동식][ 22 | - $N$개의 정수 $A_1, A_2, dots.c, A_N$이 있을 때, $A_1 + A_2 + dots.c + A_N$을 $3$으로 나눈 나머지를 구하자. 23 | 24 | - $A_i$가 최대 $10^18$이고, $N$이 최대 $10^5$ 25 | 26 | - 합이 너무 커요 ㅠㅠ 27 | 28 | - $A_i$를 직접 더하지 않고 나머지만을 구할 수 없을까? 29 | 30 | #pagebreak() 31 | 32 | - $(A + B) mod 3 = (A mod 3 + B mod 3) mod 3$ 33 | 34 | - 놀랍게도 이 성질은 $+$뿐만 아니라 $-$, $times$에도 적용되는데... 35 | 36 | - 놀랍게도 이 성질은 $3$이 아닌 모든 양의 정수에도 적용되는데... 37 | 38 | - 수식으로는 $A mod 3 = B mod 3 <=> A equiv B space (mod 3)$ 39 | 40 | - 구현 시에는 모든 수, 모든 $+$, $-$, $times$ 시마다 나머지를 취하기 41 | 42 | - $mod N$에서 모든 수의 범위는 $0$ 이상 $N$ 미만으로 줄어든다! 43 | ] 44 | 45 | #slide.slide[과제][ 46 | - #slide.problem("9711", "피보나치") 47 | 48 | - #slide.problem("4375", "1") 49 | 50 | - #slide.problem("14928", "큰 수 (BIG)") 51 | 52 | - #slide.problem("27965", "N결수") 53 | ] 54 | 55 | #slide.slide[소수 판정][ 56 | - $1$과 자기 자신만을 양의 약수로 가지는 $2$ 이상의 정수 57 | 58 | - 양의 정수 $N$이 소수인지 판단하기 59 | 60 | - $1$부터 $N$까지 전부 나누면 $cal(O)(N)$ 61 | 62 | - 조금 더 빠르게 할 수 없을까? 63 | 64 | #pagebreak() 65 | 66 | - $42$의 약수 $#text(fill: red)[1], #text(fill: green)[2], #text(fill: blue)[3], #text(fill: purple)[6], #text(fill: purple)[7], #text(fill: blue)[14], #text(fill: green)[21], #text(fill: red)[42]$ 67 | - $1 times 42 = 42$ 68 | - $2 times 21 = 42$ 69 | - $3 times 14 = 42$ 70 | - $6 times 7 = 42$ 71 | 72 | - *앞쪽* 절반만 본다면 $cal(O)(sqrt(N))$ 73 | 74 | #pagebreak() 75 | 76 | #columns(2)[ 77 | #set text(size: 20pt) 78 | 79 | #algorithm({ 80 | import algorithmic: * 81 | Function([Is-Prime], args: ([$N$], )) 82 | If( 83 | cond: $N = 1$, 84 | Return[*false*] 85 | ) 86 | For( 87 | cond: [$i = 2$ *upto* #FnI[floor][#FnI[sqrt][$N$]]], 88 | If( 89 | cond: [$N equiv 0 space (mod i)$], 90 | Return[*false*] 91 | ), 92 | ) 93 | Return[*true*] 94 | }) 95 | 96 | - `sqrt`나 `floor`는 실수 오차를 동반하고, 느릴 수 있음 97 | 98 | #colbreak() 99 | 100 | #algorithm({ 101 | import algorithmic: * 102 | Function([Is-Prime-2], args: ([$N$], )) 103 | If( 104 | cond: $N = 1$, 105 | Return[*false*] 106 | ) 107 | Assign[$i$][$2$] 108 | While( 109 | cond: [$i^2 <= N$], 110 | If( 111 | cond: [$N equiv 0 space (mod i)$], 112 | Return[*false*] 113 | ), 114 | Assign[$i$][$i + 1$], 115 | ) 116 | Return[*true*] 117 | }) 118 | 119 | - 정수 연산은 정확 120 | ] 121 | 122 | #pagebreak() 123 | 124 | - 작은 약수부터 찾아 나눌 때, 나누어지지 않을 때까지 나눠보기 125 | 126 | #algorithm({ 127 | import algorithmic: * 128 | Function([Factorize], args: ([$N$], )) 129 | Assign[$i$][$2$] 130 | While( 131 | cond: [$i^2 <= N$], 132 | While( 133 | cond: [$N equiv 0 space (mod i)$], 134 | State[*print* $i$], 135 | Assign[$N$][$N slash i$], 136 | ), 137 | Assign[$i$][$i + 1$], 138 | ) 139 | If( 140 | cond: $N eq.not 1$, 141 | State[*print* $N$], 142 | ) 143 | }) 144 | ] 145 | 146 | #slide.slide[과제][ 147 | - #slide.problem("24039", "2021은 무엇이 특별할까?") 148 | 149 | - #slide.problem("27065", "2022년이 아름다웠던 이유") 150 | 151 | - #slide.problem("28138", "재밌는 나머지 연산") 152 | ] 153 | 154 | #slide.slide[에라토스테네스의 체][ 155 | - $N$ 이하의 소수를 모두 구해야 하는 경우 $cal(O)(N sqrt(N))$ 156 | 157 | - 중복 연산이 너무 많아요 158 | 159 | - *약수를 세는 것보다 배수를 세는 것이 빠르다* 160 | 161 | #pagebreak() 162 | 163 | - $2$ 이상 $N$ 이하의 각 정수가 소수인지를 배열에 저장 164 | 165 | - 처음에는 모든 수가 소수라고 가정 166 | 167 | - 가장 작은 소수를 하나 찾으면, 그 수의 배수는 소수가 아니라고 확정 168 | 169 | - 시간복잡도는 $cal(O)(N log log N)$ 170 | 171 | #pagebreak() 172 | 173 | #h(0pt) 174 | 175 | #algorithm({ 176 | import algorithmic: * 177 | Function([Find-Primes], args: ([$N$], )) 178 | Assign[isPrime$[2..N]$][*true*] 179 | Assign[primeList][${}$] 180 | For( 181 | cond: [$i = 2$ *upto* $N$], 182 | If( 183 | cond: [isPrime$[i]$], 184 | State[*add* $i$ *to* primeList], 185 | Assign[$j$][$2 times i$], 186 | While( 187 | cond: [$j <= N$], 188 | Assign[isPrime$[j]$][*false*], 189 | Assign[$j$][$j + i$] 190 | ), 191 | ) 192 | ) 193 | Return[primeList] 194 | }) 195 | 196 | #pagebreak() 197 | 198 | - 체에 `true`, `false` 대신 그 수의 소인수를 넣는다면? 199 | 200 | #algorithm({ 201 | import algorithmic: * 202 | Function([Find-Prime-Factors], args: ([$N$], )) 203 | Assign[primeFactor$[i]$][$i$] 204 | For( 205 | cond: [$i = 2$ *upto* $N$], 206 | If( 207 | cond: [primeFactor$[i] = i$], 208 | Assign[$j$][$2 times i$], 209 | While( 210 | cond: [$j <= N$], 211 | Assign[primeFactor$[j]$][$i$], 212 | Assign[$j$][$j + i$] 213 | ), 214 | ) 215 | ) 216 | Return[primeFactor] 217 | }) 218 | ] 219 | 220 | #slide.slide[과제][ 221 | - #slide.problem("1929", "소수 구하기") 222 | 223 | - #slide.problem("2421", "저금통") 224 | 225 | - #slide.problem("16563", "어려운 소인수분해") 226 | ] -------------------------------------------------------------------------------- /basic/03-linear-data.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kiwiyou/algorithm-lecture/a176b7ef09d0c1592a0979bc7e4ffad3177160ce/basic/03-linear-data.pdf -------------------------------------------------------------------------------- /basic/03-linear-data.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/cetz:0.1.2" 2 | #import "@preview/algorithmic:0.1.0" 3 | #import "../slide.typ" 4 | #show: slide.style 5 | #show link: slide.link 6 | #show footnote.entry: slide.footnote 7 | 8 | #let algorithm(..args) = text(font: ("linux libertine", "Pretendard"), size: 17pt)[#algorithmic.algorithm(..args)] 9 | #let func(body) = text(font: ("linux libertine", "Pretendard"))[#smallcaps[#body]] 10 | 11 | #align(horizon + center)[ 12 | = 알고리즘 기초 세미나 13 | 03: 선형 자료구조 14 | 15 | #text(size: 0.8em)[ 16 | 연세대학교 전우제#super[kiwiyou] \ 17 | 2023.12.06.r1 18 | ] 19 | ] 20 | 21 | #slide.slide[선형 자료구조][ 22 | - 자료(data)의 선형 위치 관계를 준 것 23 | 24 | - 한 자료의 위치로 다른 자료의 위치를 알 수 있음 25 | 26 | - 선형 #sym.dash.em 자료끼리 일직선, 등간격 27 | 28 | #align(center)[ 29 | #cetz.canvas({ 30 | import cetz.draw: * 31 | grid((0, 0), (1.5, 6), step: 1.5, name: "vert") 32 | grid((rel: (1, -.75), to: "vert.right"), (rel: (7, .75), to: "vert.right"), step: 1.5) 33 | }) 34 | ] 35 | ] 36 | 37 | #slide.slide[정렬][ 38 | - 자료에 순서를 부여하는 방법 39 | 40 | - 자료의 위치와 자료의 순서가 연관됨 (단조성) 41 | 42 | - 중복과 관련된 처리가 용이 43 | 44 | - 일반적으로 표준 라이브러리에는 배열에 대한 $cal(O)(N log N)$ 구현 존재 45 | ] 46 | 47 | #slide.slide[과제][ 48 | - #slide.problem("27522", "카트라이더: 드리프트") 49 | 50 | - #slide.problem("29723", "브실이의 입시전략") 51 | 52 | - #slide.problem("30684", "모르고리즘 회장 정하기") 53 | ] 54 | 55 | 56 | #slide.slide[동적 배열][ 57 | - 컴퓨터는 값을 메모리 공간에 저장 58 | 59 | - 메모리 공간은 일반적으로 커다란 배열 60 | 61 | - 메모리 주소 = 배열의 인덱스 62 | - 인덱스 접근은 $cal(O)(1)$ 63 | 64 | - 자료를 하나 저장하는 데 필요한 인덱스의 개수를 알면 $cal(O)(1)$ 시간에 $i$번째 자료의 메모리 주소를 알 수 있음 65 | 66 | $ "addr"(i) = i times "count" + "base" $ 67 | 68 | #pagebreak() 69 | 70 | - 값을 저장하기 위해서는 메모리 공간의 사용을 미리 허락받아야 함 = 할당 71 | 72 | - "동적" #sym.dash.em 허락받을 공간의 크기가 자꾸 변해요 73 | 74 | #align(center)[ 75 | #cetz.canvas({ 76 | import cetz.draw: * 77 | let memory = (top-left, count, ..opt) => { 78 | group({ 79 | set-origin(top-left) 80 | grid((0, 0), (1.5 * 15, 1.5), step: 1.5) 81 | content((1.5 * (2 + count / 2), 0.75), [배열], frame: "rect", stroke: none, fill: white, padding: 0.25) 82 | rect((1.5 * 2, 0), (1.5 * (2 + count), 1.5), fill: rgb(100%, 0, 0, 20%)) 83 | content((1.5 * (7 + 6 / 2), 0.75), [사용 중인 영역], frame: "rect", stroke: none, fill: white, padding: 0.25) 84 | rect((1.5 * 7, 0,), (1.5 * (7 + 6), 1.5), fill: rgb(0, 0, 100%, 20%)) 85 | }, ..opt) 86 | } 87 | memory((0, 6), 4, name: "size-4") 88 | set-style( 89 | mark: ( 90 | end: ">", 91 | size: 0.3, 92 | fill: black, 93 | ) 94 | ) 95 | group({ 96 | set-origin("size-4.bottom") 97 | line((0, -0.25), (0, -1.25)) 98 | content((0.5, -0.75), [원소 추가], anchor: "left") 99 | }) 100 | memory((0, 3), 5, name: "size-5") 101 | group({ 102 | set-origin("size-5.bottom") 103 | line((0, -0.25), (0, -1.25)) 104 | content((0.5, -0.75), [원소 추가?], anchor: "left") 105 | }) 106 | memory((0, 0), 6) 107 | }) 108 | ] 109 | 110 | #pagebreak() 111 | 112 | - 매번 새로운 공간을 할당하고 *기존 원소를 복사* #sym.dash.em $cal(O)(N)$? 113 | 114 | - 할당하는 크기를 항상 $2^K$으로! 115 | 116 | - 크기가 $2^K$에 도달하기 위해서는 복사가 $2^0 + 2^1 + 2^2 + dots.c + 2^(K-1)$번 117 | 118 | $ sum_(i=0)^(K-1) 2^i = (2^K - 1) / (2 - 1) = 2^K - 1 $ 119 | 120 | - 크기가 $N$일 때 복사가 $cal(O)(N)$번 121 | 122 | - 크기가 $1$ 늘어날 때 전체 복사 수가 $cal(O)(1)$ 증가한다고 생각하기 123 | ] 124 | 125 | #slide.slide[스택][ 126 | - 자료구조의 한 끝을 정해, 그곳에서만 추가와 삭제가 일어나는 자료구조 127 | 128 | - 많은 언어의 동적 배열은 스택의 기능을 가짐 129 | - 자료구조의 끝에 추가 $cal(O)(1)$ 130 | - 자료구조의 끝에서 삭제 $cal(O)(1)$ 131 | 132 | - 가장 최근에 본 자료에 초점을 맞출 때 사용 133 | 134 | #pagebreak() 135 | 136 | - *짝 맞추기 유형* 137 | 138 | - 여러 종류의 괄호가 포함된 문자열에서 각 괄호의 짝이 맞는지 판단하기 139 | 140 | #align(center)[ 141 | `(The quick {brown fox} jumps over [the lazy (dog)])` \ 142 | #text(fill: red)[`(`]`The quick {brown`#text(fill: red)[`)`]` fox} jumps over {[the lazy dog]}` 143 | ] 144 | 145 | - 괄호마다 들어 있는 원소의 개수를 출력하기 146 | 147 | - 문제를 괄호 문제로 환원하는 것이 중요 148 | 149 | #pagebreak() 150 | 151 | - *스택을 정렬된 상태로 유지하기* 152 | 153 | - 어렵기 때문에 생략 154 | 155 | - 생각해보면 좋아요 156 | ] 157 | 158 | #slide.slide[과제][ 159 | - #slide.problem("30047", "함수 문자열") 160 | 161 | - #slide.problem("17952", "과제는 끝나지 않아!") 162 | 163 | - #slide.problem("11899", "괄호 끼워넣기") 164 | ] 165 | 166 | #slide.slide[덱][ 167 | - 자료구조의 양쪽 끝에서 추가와 삭제가 가능한 자료구조 ($cal(O)(1)$) 168 | 169 | - 정렬된 상태로 유지하기 외에는, 양쪽에서 추가 및 삭제하는 요구사항이 비교적 명시적 170 | ] 171 | 172 | #slide.slide[과제][ 173 | - #slide.problem("28066", "타노스는 요세푸스가 밉다") 174 | 175 | - #slide.problem("28107", "회전초밥") 176 | ] 177 | 178 | #slide.slide[누적 합][ 179 | - 수열 $a_1, a_2, dots.c, a_N$이 주어질 때, 구간 합 $a_l + a_(l+1) + dots.c + a_r$을 구하기 180 | 181 | - $cal(O)(N)$보다 빠르게 할 수 있을까? 182 | 183 | #align(center)[ 184 | #cetz.canvas({ 185 | import cetz.draw: * 186 | import cetz.decorations: brace 187 | grid((0, 0), (1.5 * 15, 1.5), step: 1.5) 188 | rect((1.5 * 2, 0), (1.5 * (2 + 6), 1.5), fill: rgb(100%, 0, 0, 20%)) 189 | rect((1.5 * 5, 0), (1.5 * (5 + 10), 1.5), fill: rgb(0, 0, 100%, 20%)) 190 | rect((1.5 * 7, 0), (1.5 * (7 + 6), 1.5), fill: rgb(100%, 100%, 0, 20%)) 191 | brace((1.5 * 2, -.2), (1.5 * (2 + 6), -.2), flip: true) 192 | brace((1.5 * 5, 1.7), (1.5 * (5 + 10), 1.7), amplitude: 1.5, pointiness: 45deg) 193 | brace((1.5 * 7, 1.7), (1.5 * (7 + 6), 1.7)) 194 | for i in range(15) { 195 | content((1.5 * (i + 0.5), 0.9), $a_#{i+1}$, anchor: "center") 196 | } 197 | }) 198 | ] 199 | 200 | - 아이디어: 겹치는 부분을 줄이려면 여러 개를 모아서 저장하면 좋지 않을까? 201 | 202 | #pagebreak() 203 | 204 | - $S_i = a_1 + a_2 + dots.c + a_i$를 저장하자! 205 | 206 | #align(center)[ 207 | #cetz.canvas({ 208 | import cetz.draw: * 209 | import cetz.decorations: brace 210 | grid((0, 0), (1.5 * 15, 1.5), step: 1.5) 211 | rect((1.5 * 2, 0), (1.5 * (2 + 6), 1.5), fill: rgb(100%, 0, 0, 20%)) 212 | rect((1.5 * 5, 0), (1.5 * (5 + 10), 1.5), fill: rgb(0, 0, 100%, 20%)) 213 | rect((1.5 * 7, 0), (1.5 * (7 + 6), 1.5), fill: rgb(100%, 100%, 0, 20%)) 214 | 215 | brace((1.5 * 2, -.2), (1.5 * (2 + 6), -.2), flip: true) 216 | let mid1 = (1.5 * 2 + 7, -1.2) 217 | grid((rel: (-1 * 8, -1), to: mid1), (rel: (0, 0), to: mid1), step: 1) 218 | content((rel: (0.5, -0.4), to: mid1), $-$, anchor: "center") 219 | grid((rel: (-1 * 2 + 3, -1), to: mid1), (rel: (3, 0), to: mid1), step: 1) 220 | 221 | brace((1.5 * 5, 1.7), (1.5 * (5 + 10), 1.7), amplitude: 3, pointiness: 45deg) 222 | let mid2 = (1.5 * 5 + 8, 4) 223 | grid((rel: (-1 * 15, 1), to: mid2), (rel: (0, 2), to: mid2), step: 1) 224 | content((rel: (0.5, 1.6), to: mid2), $-$, anchor: "center") 225 | grid((rel: (-1 * 5 + 6, 1), to: mid2), (rel: (6, 2), to: mid2), step: 1) 226 | 227 | brace((1.5 * 7, 1.7), (1.5 * (7 + 6), 1.7)) 228 | let mid3 = (1.5 * 7 + 3, 1.75) 229 | rect((rel: (-1 * 13, 1), to: mid3), (rel: (8, 2), to: mid3), stroke: none, fill: white) 230 | grid((rel: (-1 * 13, 1), to: mid3), (rel: (0, 2), to: mid3), step: 1) 231 | content((rel: (0.5, 1.6), to: mid3), $-$, anchor: "center") 232 | grid((rel: (-1 * 7 + 8, 1), to: mid3), (rel: (8, 2), to: mid3), step: 1) 233 | 234 | for i in range(15) { 235 | content((1.5 * (i + 0.5), 0.9), $a_#{i+1}$, anchor: "center") 236 | } 237 | }) 238 | ] 239 | 240 | #pagebreak() 241 | 242 | $ 243 | S_r &= a_1 + a_2 + dots.c + a_(l - 1) &+ a_l + dots.c + a_r \ 244 | S_(l - 1) &= a_1 + a_2 + dots.c + a_(l - 1) \ 245 | S_r - S_(l - 1) &= &a_l + dots.c + a_r 246 | $ 247 | 248 | - $S_i$를 $cal(O)(N)$에 미리 계산해 두면 구간 합이 $cal(O)(1)$ 249 | 250 | - 하지만 $a_i$가 중간에 바뀐다면? #sym.dash.em 고급에서 만나요 251 | ] 252 | 253 | #slide.slide[과제][ 254 | - #slide.problem("23827", "수열 (Easy)") 255 | 256 | - #slide.problem("21921", "블로그") 257 | 258 | - #slide.problem("25947", "선물할인") 259 | 260 | - #slide.problem("28427", "Tricknology") 261 | ] -------------------------------------------------------------------------------- /basic/04-search.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kiwiyou/algorithm-lecture/a176b7ef09d0c1592a0979bc7e4ffad3177160ce/basic/04-search.pdf -------------------------------------------------------------------------------- /basic/04-search.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/cetz:0.1.2" 2 | #import "@preview/algorithmic:0.1.0" 3 | #import "../slide.typ" 4 | #show: slide.style 5 | #show link: slide.link 6 | #show footnote.entry: slide.footnote 7 | 8 | #let algorithm(..args) = text(font: ("linux libertine", "Pretendard"), size: 17pt)[#algorithmic.algorithm(..args)] 9 | #let func(body) = text(font: ("linux libertine", "Pretendard"))[#smallcaps[#body]] 10 | 11 | #align(horizon + center)[ 12 | = 알고리즘 기초 세미나 13 | 04: 탐색 14 | 15 | #text(size: 0.8em)[ 16 | 연세대학교 전우제#super[kiwiyou] \ 17 | 2024.05.23.r1 18 | ] 19 | ] 20 | 21 | #slide.slide[탐색][ 22 | - 자료구조에서 자료의 위치를 찾는 것 23 | 24 | - 자료구조 내 모든 원소를 보는 전략: $cal(O)(N)$ 25 | 26 | - 특히 선형 자료구조의 경우는 *선형 탐색* 27 | ] 28 | 29 | #slide.slide[이분 탐색][ 30 | - *정렬된* 선형 자료구조에서 빠르게 탐색 31 | 32 | - 찾으려는 원소 $x$와 모든 원소 $a_i$에 대해서 셋 중 하나가 성립 33 | - $x < a_i$ 34 | - $x = a_i$ 35 | - $x > a_i$ 36 | 37 | - $x != a_i$라면 찾으려는 자료 $a_j$는 $j < i$와 $j > i$ 둘 중 하나 38 | 39 | - 찾는 구간을 반씩 줄일 수 있음 40 | 41 | - $2^f(N) >= N$에서, $f(N) = cal(O)(log_2 N) = cal(O)(log N)$ 42 | 43 | #pagebreak() 44 | 45 | #algorithm({ 46 | import algorithmic: * 47 | Function([Binary-Search], args: ($A$, $x$), { 48 | Assign[$l$][0] 49 | Assign[$r$][#FnI[len][$A$]] 50 | While( 51 | cond: $l < r$, { 52 | Assign[$m$][#FnI[midpoint][$l$, $r$]] 53 | If( 54 | cond: $A[m] < x$, 55 | Assign[$l$][$m + 1$] 56 | ) 57 | ElseIf( 58 | cond: $A[m] = x$, 59 | Return[$m$] 60 | ) 61 | Else( 62 | Assign[$r$][$m$] 63 | ) 64 | } 65 | ) 66 | }) 67 | }) 68 | 69 | - `while` 안에서 변하지 않는 속성(불변량) 찾기 70 | 71 | #pagebreak() 72 | 73 | - 빈출 유형 74 | - 배열의 정렬 상태를 유지하면서 주어진 값 $x$가 삽입될 수 있는 위치 찾기 75 | - $x$ 이상/이하/초과/미만의 최소/최대 원소 찾기 76 | - 헷갈리지 않으려면 이후에 나올 일반화 부분 참고 77 | 78 | - $x$가 배열 $A$에 존재할 것이라는 보장이 없음 79 | 80 | #pagebreak() 81 | #h(0pt) 82 | 83 | #algorithm({ 84 | import algorithmic: * 85 | Function([Binary-Search], args: ($A$, $x$), { 86 | Assign[$l$][0] 87 | Assign[$r$][#FnI[len][$A$]] 88 | While( 89 | cond: $l < r$, { 90 | Assign[$m$][#FnI[midpoint][$l$, $r$]] 91 | If( 92 | cond: $A[m] < x$, 93 | Assign[$l$][$m + 1$] 94 | ) 95 | Else( 96 | Assign[$r$][$m$] 97 | ) 98 | } 99 | ) 100 | Cmt[$l = r$] 101 | Return[$l$] 102 | }) 103 | }) 104 | 105 | - 무한루프를 피할 수 있을까? 106 | ] 107 | 108 | #slide.slide[이분 탐색의 일반화][ 109 | - 값을 찾는 구간이 `true`와 `false`로만 이루어진 대형 배열이라고 생각하기 110 | 111 | - `true` `true` ... `true` `false` `false` ... `false` 에서 첫 `false`의 위치를 찾음 112 | 113 | #align(center + horizon)[ 114 | #cetz.canvas({ 115 | import cetz.draw: * 116 | 117 | rect((0, 0), (1.5 * 4, 1.5), stroke: none, fill: rgb(0%, 100%, 0%, 20%)) 118 | rect((1.5 * 4, 0), (1.5 * 10, 1.5), stroke: none, fill: rgb(100%, 0%, 0%, 20%)) 119 | grid((0, 0), (1.5 * 10, 1.5), step: 1.5) 120 | 121 | line((1.5 * 4.5, -1.5), (1.5 * 4.5, -0.5), mark: (end: ">")) 122 | }) 123 | ] 124 | 125 | #pagebreak() 126 | #h(0pt) 127 | 128 | #algorithm({ 129 | import algorithmic: * 130 | Function([Partition-Point], args: ($f$, $min$, $max$), { 131 | Assign[$l$][$min$] 132 | Assign[$r$][$max$] 133 | While( 134 | cond: $l < r$, { 135 | Assign[$m$][#FnI[midpoint][$l$, $r$]] 136 | If( 137 | cond: $f(m)$, 138 | Assign[$l$][$m + 1$] 139 | ) 140 | Else( 141 | Assign[$r$][$m$] 142 | ) 143 | } 144 | ) 145 | Cmt[$l = r$] 146 | Return[$l$] 147 | }) 148 | }) 149 | ] 150 | 151 | #slide.slide[과제][ 152 | - #slide.problem("26258", "다중 일차 함수") 153 | 154 | - #slide.problem("27968", "사사의 사차원 사탕 봉지") 155 | 156 | - #slide.problem("28103", "대회 상품 정하기") 157 | ] 158 | 159 | #slide.slide[매개 변수 탐색][ 160 | - 조건을 만족하는 최솟값을 구하는 문제 161 | 162 | - 최솟값으로 접근해가는 방법이 명확하게 떠오르지 않을 때 163 | 164 | - 답이 단조성\*을 띨 때 사용 가능 165 | 166 | - 답을 변수 $x$로 두고 조건을 만족하는지 / 만족하지 않는지로 이분 탐색 167 | 168 | - 조건 판정에 $cal(O)(f(N))$ 시간이 걸릴 때 $cal(O)(f(N) log X)$ 시간 169 | ] 170 | 171 | #slide.slide[과제][ 172 | - #slide.problem("15810", "풍선 공장") 173 | 174 | - #slide.problem("2805", "나무 자르기") 175 | 176 | - #slide.problem("27977", "킥보드로 등교하기") 177 | ] 178 | -------------------------------------------------------------------------------- /basic/05-associated-data.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kiwiyou/algorithm-lecture/a176b7ef09d0c1592a0979bc7e4ffad3177160ce/basic/05-associated-data.pdf -------------------------------------------------------------------------------- /basic/05-associated-data.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/cetz:0.1.2" 2 | #import "@preview/algorithmic:0.1.0" 3 | #import "../slide.typ" 4 | #show: slide.style 5 | #show link: slide.link 6 | #show footnote.entry: slide.footnote 7 | 8 | #let algorithm(..args) = text(font: ("linux libertine", "Pretendard"), size: 17pt)[#algorithmic.algorithm(..args)] 9 | #let func(body) = text(font: ("linux libertine", "Pretendard"))[#smallcaps[#body]] 10 | 11 | #align(horizon + center)[ 12 | = 알고리즘 기초 세미나 13 | 05: 연관 자료구조 14 | 15 | #text(size: 0.8em)[ 16 | 연세대학교 전우제#super[kiwiyou] \ 17 | 2023.01.11.r1 18 | ] 19 | ] 20 | 21 | #slide.slide[맵][ 22 | - 두 자료가 연관되어 있음 23 | 24 | - 한 자료로 다른 쪽 자료를 찾을 수 있음 25 | 26 | - 찾는 열쇠가 되는 자료가 키, 찾으려는 자료는 값 27 | ] 28 | 29 | #slide.slide[해시를 이용한 맵][ 30 | - 해시 함수: 모든 값을 고정된 길이의 값으로 변환하는 함수 31 | 32 | - 키에 해시 함수를 적용한 값을 사용하여 값을 찾음 33 | 34 | - 가장 간단한 구현으로, 배열을 만든 후 해시 값을 길이로 나눈 나머지 위치에 값을 저장 35 | 36 | - 삽입, 검색, 삭제에 모두 평균 $cal(O)(1)$, 최악 $cal(O)(N)$ 37 | 38 | - 해시 충돌: 해시 값이 같은 키가 여럿 존재하는 경우 39 | ] 40 | 41 | #slide.slide[트리를 이용한 맵][ 42 | - 해시 함수를 이용하지 않고, 균형 잡힌 이진 검색 트리#super([BBST])를 이용하여 연관을 구현 43 | 44 | - 삽입, 검색 및 삭제에 모두 $cal(O)(log N)$ 45 | 46 | - 키에 관한 이분 탐색이 가능 47 | ] 48 | 49 | #slide.slide[집합 자료구조][ 50 | - 키와 연관된 값 없이 자료의 존재 여부만 확인할 수 있는 자료구조 51 | ] 52 | 53 | #slide.slide[과제][ 54 | - #slide.problem("26069", "붙임성 좋은 총총이") 55 | 56 | - #slide.problem("29721", "변형 체스 놀이 : 다바바(Dabbaba)") 57 | 58 | - #slide.problem("29714", "브실이의 구슬 아이스크림") 59 | ] 60 | 61 | #slide.slide[우선순위 큐][ 62 | - 자료마다 우선순위가 부여됨 63 | 64 | - 삽입된 값 중 우선순위가 높은 값이 먼저 삭제됨\* 65 | 66 | - 보통 이진 힙으로 구현: 삽입, 삭제에 $cal(O)(log N)$ 67 | ] 68 | 69 | #slide.slide[우선순위 큐][ 70 | - #slide.problem("14235", "크리스마스 선물") 71 | 72 | - #slide.problem("23757", "아이들과 선물 상자") 73 | 74 | - #slide.problem("28107", "회전초밥") 75 | ] 76 | -------------------------------------------------------------------------------- /basic/06-optimization.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kiwiyou/algorithm-lecture/a176b7ef09d0c1592a0979bc7e4ffad3177160ce/basic/06-optimization.pdf -------------------------------------------------------------------------------- /basic/06-optimization.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/cetz:0.1.2" 2 | #import "@preview/algorithmic:0.1.0" 3 | #import "../slide.typ" 4 | #show: slide.style 5 | #show link: slide.link 6 | #show footnote.entry: slide.footnote 7 | 8 | #let algorithm(..args) = text(font: ("linux libertine", "Pretendard"), size: 17pt)[#algorithmic.algorithm(..args)] 9 | #let func(body) = text(font: ("linux libertine", "Pretendard"))[#smallcaps[#body]] 10 | 11 | #align(horizon + center)[ 12 | = 알고리즘 기초 세미나 13 | 06: 최적화 문제의 접근 방법 14 | 15 | #text(size: 0.8em)[ 16 | 연세대학교 전우제#super[kiwiyou] \ 17 | 2023.01.25.r1 18 | ] 19 | ] 20 | 21 | #slide.slide[그리디][ 22 | - 문제가 단계별로 나누어져 있을 때, 각 단계의 최적을 통해 전체의 최적을 구하는 방법 23 | - $N$개의 정수 중 합이 가장 작은 세 수를 고르기 24 | - $cal(O)(N^3)$ 25 | - $cal(O)(N^2)$ 26 | - $cal(O)(N)$ 27 | 28 | - $K$개 수 고르기 29 | - $cal(O)(N log N)$ 30 | - $cal(O)(N)$ 31 | 32 | #pagebreak() 33 | 34 | - $N$명의 사람이 한 화장실을 $A_(i)$초 이용할 때, 각 사람이 화장실을 다 쓰고 나올 때까지 걸린 시간의 합의 최솟값 35 | 36 | - $A_(i)$를 적절히 재배열한 수열 $B_(i)$에 대해서, 기다리는 시간의 합은 37 | 38 | $ N B_(1) + (N - 1) B_(2) + dots.c + 1 B_(N) $ 39 | 40 | - 직관적으로? 41 | 42 | #pagebreak() 43 | 44 | - 교환 논법#super[Exchange Argument] 45 | 46 | - 최소가 되는 배치 $B$에서, 어떤 인접한 두 원소 $B_(i)$와 $B_(j)$가 $i < j$이고 $B_(i) >= B_(j)$를 만족한다고 가정 47 | 48 | - 필요한 시간은 $2B_(i) + B_(j)$ 49 | 50 | - 순서를 바꾸면 $B_(i) + 2B_(j) <= 2B_(i) + B_(j)$ 51 | 52 | - 앞쪽이 작도록 정렬하는 게 절대 손해가 되지 않음: 반드시 최적해 중 하나! 53 | 54 | #pagebreak() 55 | 56 | - 시작 시각이 $S_(i)$, 종료 시각이 $E_(i)$인 회의 $N$개가 하나의 회의실을 사용하려고 할 때, 진행 가능한 회의의 최대 수 57 | 58 | - 길이 $N$의 문자열이 `(`, `)`, `?`로 이루어져 있을 때, `?`를 모두 적당히 바꿔서 괄호 짝이 모두 맞도록 하기 59 | 60 | - $N$종류의 동전이 있을 때 $M$원을 거슬러 주기 위한 동전 수의 최솟값 61 | ] 62 | 63 | #slide.slide[과제][ 64 | - #slide.problem("29615", "알파빌과 베타빌") 65 | 66 | - #slide.problem("29767", "점수를 최대로") 67 | 68 | - #slide.problem("28353", "고양이 카페") 69 | ] 70 | 71 | #slide.slide[재귀][ 72 | - 자기 자신을 참조하는 것 73 | 74 | - 크기 $M$의 문제를 풀어서 크기 $N > M$의 문제를 풀 수 있는 경우 75 | 76 | - 주로 그리디로 풀기 어려운 문제를 해결 77 | 78 | - $n! = n times (n - 1)!$ 79 | 80 | - $F_(n) = F_(n - 1) + F_(n - 2)$ 81 | 82 | #pagebreak() 83 | 84 | - 원판이 $N$개인 하노이 탑을 옮기는 방법 85 | 86 | - $N - 1$개인 탑을 옮긴다 87 | 88 | - 원판을 옮긴다 89 | 90 | - $N - 1$개인 탑을 다시 옮긴다 91 | 92 | #pagebreak() 93 | 94 | - 함수의 동작 과정 대신 의미 혹은 반환값만을 생각해야 헷갈리지 않음 95 | 96 | #algorithm({ 97 | import algorithmic: * 98 | Function([Hanoi], args: ($N$, $"from"$, $"to"$, $"mid"$), { 99 | If(cond: $N = 0$, { 100 | Return[] 101 | }) 102 | Call([Hanoi], [$N$, $"from"$, $"mid"$, $"to"$]) 103 | State[*print* $"from" -> "to"$] 104 | Call([Hanoi], [$N$, $"mid"$, $"to"$, $"from"$]) 105 | }) 106 | }) 107 | 108 | - $cal(O)(2^N)$ 109 | ] 110 | 111 | #slide.slide[과제][ 112 | - #slide.problem("1074", "Z") 113 | ] 114 | 115 | #slide.slide[다이나믹 프로그래밍][ 116 | - 문제를 재귀 형식으로 풀 때, 같은 인자를 주어 여러 번 실행하는 경우 117 | 118 | $ 119 | f(n) &= f(n - 1) + f(n - 2) \ 120 | f(2) &= 1 \ 121 | f(1) &= 1 122 | $ 123 | 124 | - $f(6)$을 구하기 위한 $f(1)$의 실행 횟수는? 125 | 126 | - -> 배열에 함숫값을 저장해 두자! 127 | 128 | #pagebreak() 129 | 130 | - $cal(O)(n)$ 131 | 132 | #algorithm({ 133 | import algorithmic: * 134 | Function([Fibonacci], args: ($n$, $"cache"$), { 135 | If(cond: [$"cache"$ *not contains* $n$], { 136 | Assign[$"cache"[n]$][ 137 | #CallI("Fibonacci", [$n - 1$, $"cache"$]) + #CallI("Fibonacci", [$n - 2$, $"cache"$]) 138 | ] 139 | }) 140 | Return[$"cache"[n]$] 141 | }) 142 | }) 143 | 144 | #algorithm({ 145 | import algorithmic: * 146 | Function([Fibonacci], args: ($n$, ), { 147 | Assign[$"cache"[1]$][$1$] 148 | Assign[$"cache"[2]$][$1$] 149 | For(cond: [$i$ *from* $3$ *upto* $n$], { 150 | Assign[$"cache"[i]$][$"cache"[i - 1]$ + $"cache"[i - 2]$] 151 | }) 152 | Return[$"cache"$[n]] 153 | }) 154 | }) 155 | ] 156 | 157 | #slide.slide[과제][ 158 | - #slide.problem("14916", "거스름돈") 159 | 160 | - #slide.problem("9656", "돌 게임 2") 161 | 162 | - #slide.problem("9095", "1, 2, 3 더하기") 163 | ] 164 | -------------------------------------------------------------------------------- /basic/07-graph.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kiwiyou/algorithm-lecture/a176b7ef09d0c1592a0979bc7e4ffad3177160ce/basic/07-graph.pdf -------------------------------------------------------------------------------- /basic/07-graph.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/cetz:0.1.2" 2 | #import "@preview/algorithmic:0.1.0" 3 | #import "../slide.typ" 4 | #show: slide.style 5 | #show link: slide.link 6 | #show footnote.entry: slide.footnote 7 | 8 | #let algorithm(..args) = text(font: ("linux libertine", "Pretendard"), size: 17pt)[#algorithmic.algorithm(..args)] 9 | #let func(body) = text(font: ("linux libertine", "Pretendard"))[#smallcaps[#body]] 10 | 11 | #align(horizon + center)[ 12 | = 알고리즘 기초 세미나 13 | 07: 그래프 14 | 15 | #text(size: 0.8em)[ 16 | 연세대학교 전우제#super[kiwiyou] \ 17 | 2023.02.01.r1 18 | ] 19 | ] 20 | 21 | #slide.slide[그래프][ 22 | - $V$개의 정점과 $E$개의 간선의 집합 23 | 24 | - 한 간선은 두 정점을 연결 25 | 26 | - 정점에는 번호가 있을 수 있고, 간선에는 방향과 가중치가 있을 수 있음 27 | 28 | #pagebreak() 29 | 30 | - 간선의 존재 여부 31 | 32 | - 정점에 연결된 간선 탐색 33 | 34 | #pagebreak() 35 | 36 | - 인접 행렬 37 | 38 | - $V times V$ 행렬 $[a_(i j)]$에서, $i -> j$ 간선이 있으면 $a_(i j) = 1$, 아니면 $0$ 39 | 40 | - $V times V$ 행렬 $[a_(i j)]$에서, $i -> j$ 간선의 가중치가 $w$라면 $a_(i j) = w$ 41 | 42 | - 간선의 존재 여부 $cal(O)(1)$ 시간 43 | 44 | - 정점에 연결된 간선 탐색 $cal(O)(V)$ 시간 45 | 46 | - 두 정점 사이에는 최대 한 개의 간선 47 | 48 | - 공간복잡도 $cal(O)(V^2)$ 49 | 50 | #pagebreak() 51 | 52 | - 인접 리스트 53 | 54 | - $V$개의 리스트 $L[u]$를 관리 55 | 56 | - $L[u]$는 $u -> v$ 간선이 존재하는 모든 $v$ (와 간선 가중치 $w$)의 리스트 57 | 58 | - 간선의 존재 여부 $cal(O)(E)$ 시간 59 | 60 | - 정점에 연결된 간선 탐색 $cal(O)(E)$ 시간 61 | 62 | - 공간복잡도 $cal(O)(V + E)$ 63 | 64 | #pagebreak() 65 | 66 | - 그래프 탐색: 연결된 정점들을 한 번씩 방문 67 | 68 | - 깊이 우선 탐색: 연결되어 있고 아직 방문하지 않은 정점을 하나 골라 탐색 69 | 70 | - 너비 우선 탐색: 아직 방문하지 않은, 출발지로부터 가까운 정점부터 탐색 71 | 72 | #pagebreak() 73 | 74 | - 깊이 우선 탐색 75 | 76 | #algorithm({ 77 | import algorithmic: * 78 | Function("Depth-First-Search", args: ($u$, $E$, $"visited"$), { 79 | Assign[$"visited"[u]$][*true*] 80 | For(cond: [*edge* $(u, v)$ *in* $E$], { 81 | If(cond: [*not* $"visited"[v]$], { 82 | Call("Depth-First-Search", [$v$, $E$, $"visited"$]) 83 | }) 84 | }) 85 | }) 86 | }) 87 | 88 | #pagebreak() 89 | 90 | - 너비 우선 탐색 91 | 92 | #algorithm({ 93 | import algorithmic: * 94 | Function("Breadth-First-Search", args: ($S$, $E$), { 95 | Assign[$Q$][#CallI("Make-Queue", [$S$])] 96 | Assign[$"visited"[u]$][*false*] 97 | Assign[$"visited"[u in S]$][*true*] 98 | While(cond: [$Q$ *is not empty*], { 99 | State[*pop* $u$ *from* $Q$] 100 | For(cond: [*edge* $(u, v)$ *in* $E$], { 101 | If(cond: [*not* $"visited"[v]$], { 102 | Assign[$"visited"[v]$][*true*] 103 | State[*push* $v$ *to* $Q$] 104 | }) 105 | }) 106 | }) 107 | }) 108 | }) 109 | 110 | - 방문 체크 시점에 주의 111 | ] -------------------------------------------------------------------------------- /slide.typ: -------------------------------------------------------------------------------- 1 | #let style(body) = { 2 | set page(paper: "presentation-16-9", margin: 2em) 3 | set text(size: 25pt, font: "Pretendard", lang: "ko") 4 | set footnote(numbering: "1)") 5 | body 6 | } 7 | 8 | #let slide(title, body) = { 9 | set page(header: [= #title], margin: (top: 4em)) 10 | body 11 | } 12 | 13 | #let problem(id, title) = link("https://www.acmicpc.net/problem/" + id)[#id #title] 14 | 15 | #let link(body) = text(fill: blue)[#underline(stroke: blue)[#body]] 16 | 17 | #let footnote(body) = text(size: 0.7em, body) 18 | 19 | #let algorithm(caption, body) = text(size: 15pt)[ 20 | #figure(body, caption: caption, kind: "algorithm", supplement: body => strong[알고리즘]) 21 | ] -------------------------------------------------------------------------------- /solution/11660/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | using namespace std; 4 | int main() { 5 | cin.tie(nullptr)->sync_with_stdio(false); 6 | int N, M; 7 | cin >> N >> M; 8 | vector> A(N, vector(N)); 9 | for (auto &Ai : A) 10 | for (auto &Aij : Ai) 11 | cin >> Aij; 12 | for (int r = 1; r < N; ++r) 13 | for (int c = 0; c < N; ++c) 14 | A[r][c] += A[r - 1][c]; 15 | for (int r = 0; r < N; ++r) 16 | for (int c = 1; c < N; ++c) 17 | A[r][c] += A[r][c - 1]; 18 | for (int i = 0; i < M; ++i) { 19 | int x1, y1, x2, y2; 20 | cin >> x1 >> y1 >> x2 >> y2, x1--, y1--, x2--, y2--; 21 | int s = A[x2][y2]; 22 | if (x1 > 0) 23 | s -= A[x1 - 1][y2]; 24 | if (y1 > 0) 25 | s -= A[x2][y1 - 1]; 26 | if (x1 > 0 && y1 > 0) 27 | s += A[x1 - 1][y1 - 1]; 28 | cout << s << '\n'; 29 | } 30 | } -------------------------------------------------------------------------------- /solution/11660/main.py: -------------------------------------------------------------------------------- 1 | def main(): 2 | input = open(0, "rb").readline 3 | N, M = map(int, input().split()) 4 | A = [[*map(int, input().split())] for _ in range(N)] 5 | for r in range(1, N): 6 | for c in range(N): 7 | A[r][c] += A[r - 1][c] 8 | for r in range(N): 9 | for c in range(1, N): 10 | A[r][c] += A[r][c - 1] 11 | for _ in range(M): 12 | x1, y1, x2, y2 = map(int, input().split()) 13 | x1 -= 2 14 | y1 -= 2 15 | x2 -= 1 16 | y2 -= 1 17 | s = A[x2][y2] 18 | if x1 >= 0: 19 | s -= A[x1][y2] 20 | if y1 >= 0: 21 | s -= A[x2][y1] 22 | if x1 >= 0 and y1 >= 0: 23 | s += A[x1][y1] 24 | print(s) 25 | 26 | 27 | main() 28 | -------------------------------------------------------------------------------- /solution/11660/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | use std::fmt::Write; 3 | use std::io::*; 4 | let stdin = read_to_string(stdin().lock()).unwrap(); 5 | let mut tokens = stdin.split_whitespace(); 6 | let mut next = || tokens.next().unwrap(); 7 | let n = next().parse::().unwrap(); 8 | let m = next().parse::().unwrap(); 9 | let mut a = vec![]; 10 | let mut stdout = String::new(); 11 | for _ in 0..n { 12 | let mut ai = vec![]; 13 | for _ in 0..n { 14 | let aij = next().parse::().unwrap(); 15 | ai.push(aij); 16 | } 17 | a.push(ai); 18 | } 19 | for r in 1..n { 20 | for c in 0..n { 21 | a[r][c] += a[r - 1][c]; 22 | } 23 | } 24 | for r in 0..n { 25 | for c in 1..n { 26 | a[r][c] += a[r][c - 1]; 27 | } 28 | } 29 | for _ in 0..m { 30 | let x1 = next().parse::().unwrap() - 1; 31 | let y1 = next().parse::().unwrap() - 1; 32 | let x2 = next().parse::().unwrap() - 1; 33 | let y2 = next().parse::().unwrap() - 1; 34 | let mut s = a[x2][y2]; 35 | if x1 > 0 && y1 > 0 { 36 | s += a[x1 - 1][y1 - 1]; 37 | } 38 | if x1 > 0 { 39 | s -= a[x1 - 1][y2]; 40 | } 41 | if y1 > 0 { 42 | s -= a[x2][y1 - 1]; 43 | } 44 | writeln!(stdout, "{s}").ok(); 45 | } 46 | print!("{stdout}"); 47 | } 48 | -------------------------------------------------------------------------------- /solution/14889/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | using namespace std; 4 | int find_min_diff(vector &start, vector &link, int i, 5 | vector> &S) { 6 | int half = S.size() / 2; 7 | if (start.size() == half && link.size() == half) { 8 | int diff = 0; 9 | for (auto &a : start) 10 | for (auto &b : start) 11 | diff += S[a][b]; 12 | for (auto &a : link) 13 | for (auto &b : link) 14 | diff -= S[a][b]; 15 | return abs(diff); 16 | } 17 | int ans = 1 << 30; 18 | if (start.size() < half) { 19 | start.push_back(i); 20 | ans = min(ans, find_min_diff(start, link, i + 1, S)); 21 | start.pop_back(); 22 | } 23 | if (link.size() < half) { 24 | link.push_back(i); 25 | ans = min(ans, find_min_diff(start, link, i + 1, S)); 26 | link.pop_back(); 27 | } 28 | return ans; 29 | } 30 | 31 | int main() { 32 | cin.tie(nullptr)->sync_with_stdio(false); 33 | int N; 34 | cin >> N; 35 | vector> S(N, vector(N)); 36 | for (auto &Si : S) 37 | for (auto &Sij : Si) 38 | cin >> Sij; 39 | vector start, link; 40 | cout << find_min_diff(start, link, 0, S); 41 | } -------------------------------------------------------------------------------- /solution/14889/main.py: -------------------------------------------------------------------------------- 1 | def find_min_diff(start, link, i, S): 2 | half = len(S) // 2 3 | if len(start) == half and len(link) == half: 4 | diff = 0 5 | for a in start: 6 | for b in start: 7 | diff += S[a][b] 8 | for a in link: 9 | for b in link: 10 | diff -= S[a][b] 11 | return abs(diff) 12 | ans = 1 << 30 13 | if len(start) < half: 14 | start.append(i) 15 | ans = min(ans, find_min_diff(start, link, i + 1, S)) 16 | start.pop() 17 | if len(link) < half: 18 | link.append(i) 19 | ans = min(ans, find_min_diff(start, link, i + 1, S)) 20 | link.pop() 21 | return ans 22 | 23 | def main(): 24 | input = open(0, 'rb').readline 25 | N = int(input()) 26 | S = [[*map(int, input().split())] for _ in range(N)] 27 | print(find_min_diff([], [], 0, S)) 28 | 29 | main() -------------------------------------------------------------------------------- /solution/14889/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | use std::io::*; 3 | let stdin = read_to_string(stdin().lock()).unwrap(); 4 | let mut tokens = stdin.split_whitespace(); 5 | let mut next = || tokens.next().unwrap(); 6 | let n = next().parse::().unwrap(); 7 | let mut s = vec![]; 8 | for _ in 0..n { 9 | let mut si = vec![]; 10 | for _ in 0..n { 11 | let sij = next().parse::().unwrap(); 12 | si.push(sij); 13 | } 14 | s.push(si); 15 | } 16 | println!("{}", find_min_diff(&mut vec![], &mut vec![], 0, &s)); 17 | } 18 | 19 | fn find_min_diff(start: &mut Vec, link: &mut Vec, i: usize, S: &[Vec]) -> i32 { 20 | let half = S.len() / 2; 21 | if start.len() == half && link.len() == half { 22 | let mut diff = 0; 23 | for &a in start.iter() { 24 | for &b in start.iter() { 25 | diff += S[a][b]; 26 | } 27 | } 28 | for &a in link.iter() { 29 | for &b in link.iter() { 30 | diff -= S[a][b]; 31 | } 32 | } 33 | return diff.abs(); 34 | } 35 | let mut ans = i32::MAX; 36 | if start.len() < half { 37 | start.push(i); 38 | ans = ans.min(find_min_diff(start, link, i + 1, S)); 39 | start.pop(); 40 | } 41 | if link.len() < half { 42 | link.push(i); 43 | ans = ans.min(find_min_diff(start, link, i + 1, S)); 44 | link.pop(); 45 | } 46 | ans 47 | } 48 | -------------------------------------------------------------------------------- /solution/14928/Main.java: -------------------------------------------------------------------------------- 1 | import java.io.*; 2 | 3 | public class Main { 4 | static BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in), 1048576); 5 | static StringBuilder stdout = new StringBuilder(); 6 | public static void main(String[] args) throws Exception { 7 | String N = stdin.readLine(); 8 | int remainder = 0; 9 | for (char c : N.toCharArray()) { 10 | remainder = (remainder * 10 + (int) c - (int) '0') % 20000303; 11 | } 12 | stdout.append(remainder); 13 | System.out.println(stdout); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /solution/14928/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | using namespace std; 4 | int main() { 5 | ios_base::sync_with_stdio(false); 6 | cin.tie(nullptr); 7 | 8 | string N; 9 | cin >> N; 10 | int remainder = 0; 11 | for (auto c : N) { 12 | remainder = (remainder * 10 + c - '0') % 20000303; 13 | } 14 | cout << remainder; 15 | } 16 | -------------------------------------------------------------------------------- /solution/14928/main.js: -------------------------------------------------------------------------------- 1 | const { readFileSync } = require("fs"); 2 | const token = readFileSync(0, { encoding: "ascii" }) 3 | .split(/\s+/) 4 | [Symbol.iterator](); 5 | const line = []; 6 | 7 | const N = token.next().value; 8 | let remainder = 0; 9 | for (let i = 0; i < N.length; ++i) { 10 | remainder = (remainder * 10 + N.codePointAt(i) - 48) % 20000303; 11 | } 12 | line.push(remainder); 13 | console.log(line.join("\n")); 14 | -------------------------------------------------------------------------------- /solution/14928/main.py: -------------------------------------------------------------------------------- 1 | def main(): 2 | input = open(0, "rb").readline 3 | N = input().rstrip() 4 | remainder = 0 5 | for c in N: 6 | remainder = (remainder * 10 + c - ord("0")) % 20000303 7 | print(remainder) 8 | 9 | 10 | if __name__ == "__main__": 11 | main() 12 | -------------------------------------------------------------------------------- /solution/15552/Main.java: -------------------------------------------------------------------------------- 1 | import java.io.*; 2 | 3 | public class Main { 4 | static BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in), 1048576); 5 | static StringBuilder stdout = new StringBuilder(); 6 | public static void main(String[] args) throws Exception { 7 | int T = Integer.parseInt(stdin.readLine()); 8 | for (int i = 0; i < T; ++i) { 9 | String[] token = stdin.readLine().split(" "); 10 | int a = Integer.parseInt(token[0]); 11 | int b = Integer.parseInt(token[1]); 12 | stdout.append(a + b).append('\n'); 13 | } 14 | System.out.println(stdout); 15 | } 16 | } -------------------------------------------------------------------------------- /solution/15552/README.md: -------------------------------------------------------------------------------- 1 | # [15552 빠른 A+B](https://acmicpc.net/problem/15552) 2 | 3 | - 입력과 출력을 빠르게 받아야 합니다. 4 | - 언어별로 입출력을 빠르게 수행하는 방법을 정리해 두었습니다. 5 | - 자세한 풀이는 코드를 참고하세요. 6 | 7 | ## C++ 8 | 9 | `std::cin`과 `std::cout`으로 입출력을 수행할 때 두 가지 문제가 있습니다. 10 | 11 | 1. `std::endl`을 출력하면 `std::cout`의 속도 저하가 있습니다. (아래 [버퍼링](#버퍼링) 참고) 12 | 2. `std::cin` 및 `std::cout`은 `std::scanf`와 `std::printf`와 동기화되므로 13 | 속도 저하가 있습니다. 14 | 3. `std::cin`과 `std::cout`을 번갈아 가며 사용할 때, 반드시 입력 전 출력이 15 | 모두 진행되도록 설정되어 있어 속도 저하가 있습니다. 16 | 17 | 1번 문제는 `std::endl` 대신 `"\n"`을 출력하는 것으로 해결할 수 있고, 18 | 2번 문제와 3번 문제는 `main` 함수 처음에 다음 두 줄을 추가하여 해결할 수 있습니다. 19 | ```cpp 20 | std::ios_base::sync_with_stdio(false); 21 | std::cin.tie(nullptr); 22 | ``` 23 | 24 | ## Python 25 | 26 | `input()` 함수로 많은 줄을 입력받는 것이 느립니다. 27 | 다음과 같이 `input()` 함수를 재정의하여 속도를 개선할 수 있습니다. 28 | 이때, 기존 `input()` 함수와 달리 문자열 끝에 `"\n"`이 붙을 수 있습니다. 29 | 30 | ```py 31 | input = open(0, 'rb').readline 32 | ``` 33 | 34 | ## Java 35 | 36 | `System.in`으로 직접 입력받거나 `Scanner`을 사용하는 것이 느립니다. 37 | `BufferedReader`을 사용하는 것이 좋습니다. 38 | 39 | `System.out.println`으로 매번 출력하는 것은 느립니다. (아래 [버퍼링](#버퍼링) 참고) 40 | `StringBuilder`에 모든 출력을 넣고 마지막에만 `System.out.print`를 호출하는 것이 좋습니다. 41 | 42 | 43 | ## Javascript 44 | 45 | 입력 자체가 쉽지 않아, 풀이 코드를 참고하기 바랍니다. 46 | 47 | `console.log`로 매번 출력하는 것은 느립니다. 48 | 출력할 각 줄을 배열에 저장해 두고 마지막에 `console.log`로 출력하는 것이 좋습니다. 49 | 50 | ## 버퍼링 51 | 52 | 콘솔 및 파일에 문자를 쓰는 작업은 메모리에 쓰는 작업에 비해 느린 작업입니다. 53 | 따라서 매번 콘솔 및 파일에 쓰기보다는 메모리에 일정량을 쓴 후 한꺼번에 콘솔 54 | 및 파일에 쓰는 것이 빠릅니다. 이때 메모리를 **버퍼**, 55 | 메모리에 쓰는 것을 **버퍼링**이라고 합니다. -------------------------------------------------------------------------------- /solution/15552/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace std; 3 | int main() { 4 | ios_base::sync_with_stdio(false); 5 | cin.tie(nullptr); 6 | 7 | int T; 8 | cin >> T; 9 | 10 | for (int i = 0; i < T; ++i) { 11 | int a, b; 12 | cin >> a >> b; 13 | 14 | cout << a + b << "\n"; 15 | } 16 | } -------------------------------------------------------------------------------- /solution/15552/main.js: -------------------------------------------------------------------------------- 1 | const { readFileSync } = require("fs"); 2 | const token = readFileSync(0, { encoding: "ascii" }) 3 | .split(/\s+/) 4 | [Symbol.iterator](); 5 | const line = []; 6 | 7 | const T = +token.next().value; 8 | for (let i = 0; i < T; ++i) { 9 | const a = +token.next().value; 10 | const b = +token.next().value; 11 | line.push(a + b); 12 | } 13 | console.log(line.join("\n")); 14 | -------------------------------------------------------------------------------- /solution/15552/main.py: -------------------------------------------------------------------------------- 1 | def main(): 2 | input = open(0, 'rb').readline 3 | T = int(input()) 4 | for _ in range(T): 5 | a, b = map(int, input().split()) 6 | print(a + b) 7 | 8 | if __name__ == "__main__": 9 | main() -------------------------------------------------------------------------------- /solution/15649/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | using namespace std; 4 | 5 | void print_seq(vector &is_selected, vector &order, int N, int M) { 6 | if (M == 0) { 7 | for (auto &v : order) 8 | cout << v << ' '; 9 | cout << '\n'; 10 | } else { 11 | for (int i = 0; i < N; i++) 12 | if (!is_selected[i]) { 13 | is_selected[i] = true; 14 | order.push_back(i + 1); 15 | print_seq(is_selected, order, N, M - 1); 16 | order.pop_back(); 17 | is_selected[i] = false; 18 | } 19 | } 20 | } 21 | 22 | int main() { 23 | cin.tie(nullptr)->sync_with_stdio(false); 24 | int N, M; 25 | cin >> N >> M; 26 | vector is_selected(N); 27 | vector order; 28 | print_seq(is_selected, order, N, M); 29 | } -------------------------------------------------------------------------------- /solution/15649/main.py: -------------------------------------------------------------------------------- 1 | def print_seq(is_selected, order, N, M): 2 | if M == 0: 3 | print(*order) 4 | else: 5 | for i in range(N): 6 | if not is_selected[i]: 7 | is_selected[i] = True 8 | order.append(i + 1) 9 | print_seq(is_selected, order, N, M - 1) 10 | order.pop() 11 | is_selected[i] = False 12 | 13 | def main(): 14 | N, M = map(int, input().split()) 15 | print_seq([False] * N, [], N, M) 16 | 17 | main() -------------------------------------------------------------------------------- /solution/15649/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | use std::io::*; 3 | let stdin = read_to_string(stdin().lock()).unwrap(); 4 | let mut tokens = stdin.split_whitespace(); 5 | let mut next = || tokens.next().unwrap(); 6 | let mut stdout = String::new(); 7 | let n = next().parse::().unwrap(); 8 | let m = next().parse::().unwrap(); 9 | print_seq(&mut stdout, &mut vec![false; n], &mut vec![], n, m); 10 | print!("{stdout}"); 11 | } 12 | 13 | fn print_seq( 14 | stdout: &mut String, 15 | is_selected: &mut [bool], 16 | order: &mut Vec, 17 | n: usize, 18 | m: usize, 19 | ) { 20 | use std::fmt::*; 21 | if m == 0 { 22 | for &v in order.iter() { 23 | write!(stdout, "{v} ").ok(); 24 | } 25 | writeln!(stdout).ok(); 26 | } else { 27 | for i in 0..n { 28 | if !is_selected[i] { 29 | is_selected[i] = true; 30 | order.push(i + 1); 31 | print_seq(stdout, is_selected, order, n, m - 1); 32 | order.pop(); 33 | is_selected[i] = false; 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /solution/1929/Main.java: -------------------------------------------------------------------------------- 1 | import java.io.*; 2 | 3 | public class Main { 4 | static BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in), 1048576); 5 | static StringBuilder stdout = new StringBuilder(); 6 | public static void main(String[] args) throws Exception { 7 | String[] token = stdin.readLine().split(" "); 8 | int M = Integer.parseInt(token[0]); 9 | int N = Integer.parseInt(token[1]); 10 | boolean[] is_not_prime = new boolean[N + 1]; 11 | for (int i = 2; i <= N; ++i) { 12 | if (!is_not_prime[i]) { 13 | if (i >= M) { 14 | stdout.append(i).append('\n'); 15 | } 16 | for (int j = 2 * i; j <= N; j += i) { 17 | is_not_prime[j] = true; 18 | } 19 | } 20 | } 21 | System.out.println(stdout); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /solution/1929/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | using namespace std; 4 | int main() { 5 | ios_base::sync_with_stdio(false); 6 | cin.tie(nullptr); 7 | 8 | int M, N; 9 | cin >> M >> N; 10 | 11 | vector is_prime(N + 1, true); 12 | for (int i = 2; i <= N; ++i) { 13 | if (is_prime[i]) { 14 | if (i >= M) { 15 | cout << i << '\n'; 16 | } 17 | for (int j = 2 * i; j <= N; j += i) { 18 | is_prime[j] = false; 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /solution/1929/main.js: -------------------------------------------------------------------------------- 1 | const { readFileSync } = require("fs"); 2 | const token = readFileSync(0, { encoding: "ascii" }) 3 | .split(/\s+/) 4 | [Symbol.iterator](); 5 | const line = []; 6 | 7 | const M = +token.next().value; 8 | const N = +token.next().value; 9 | const is_prime = Array.from({length: N + 1}).map(() => true); 10 | for (let i = 2; i <= N; ++i) { 11 | if (is_prime[i]) { 12 | if (i >= M) { 13 | line.push(i); 14 | } 15 | for (let j = 2 * i; j <= N; j += i) { 16 | is_prime[j] = false; 17 | } 18 | } 19 | } 20 | console.log(line.join("\n")); 21 | -------------------------------------------------------------------------------- /solution/1929/main.py: -------------------------------------------------------------------------------- 1 | def main(): 2 | import itertools as it 3 | 4 | M, N = map(int, input().split()) 5 | c = memoryview(bytearray([1]) * 499999) 6 | f = memoryview(bytearray(499999)) 7 | for i in range(499): 8 | if c[i]: 9 | c[2 * i * (i + 3) + 3 :: 2 * i + 3] = f[2 * i * (i + 3) + 3 :: 2 * i + 3] 10 | if M <= 2 <= N: 11 | print(2) 12 | M = 3 13 | p = (M - 2) // 2 14 | q = (N - 3) // 2 + 1 15 | print("\n".join(map(str, it.compress(range(p * 2 + 3, q * 2 + 3, 2), c[p:q])))) 16 | 17 | 18 | main() 19 | -------------------------------------------------------------------------------- /solution/2040/README.md: -------------------------------------------------------------------------------- 1 | # 2040 수 게임 2 | 3 | 게임에서 승리하려면 내가 선택한 수들의 합이 상대가 선택한 수들의 합보다 작아야 합니다. 따라서, 승리에 영향을 주는 변수는 내가 선택한 수들의 합과 상대가 선택한 수들의 합의 차로, 음수가 가능하다면 나의 승리이고, 잘해도 0이라면 비기며, 양수면 패배하게 됩니다. 이 차가 작을수록 다음 차례에도 내가 이길 가능성이 높기 때문에, 차를 최소로 유지해야 합니다. 4 | 5 | 내가 $i$번째 차례에서 얻을 수 있는 최소 차를 $d_i$라고 합시다. $d_{i+1}$에 도달하기 위해서 원하는 만큼의 $a_j$를 고를 수 있으므로, 6 | 7 | $$ d_{i+1} = \min_{0 \le j \le i} \left( \sum_{k = j+1}^{i+1} a_k - d_j \right) $$ 8 | 9 | $n \le 3000$이므로 $a_k$의 합을 매번 직접 구하면 시간이 부족합니다. 누적 합을 이용해서 $\mathcal{O}(1)$로 최적화할 수 있습니다. -------------------------------------------------------------------------------- /solution/2040/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | using namespace std; 4 | int main() { 5 | cin.tie(nullptr)->sync_with_stdio(false); 6 | int N; 7 | cin >> N; 8 | for (int i = 0; i < 3; ++i) { 9 | vector min_diff = {0}; 10 | vector prefix_sum = {0}; 11 | int prefix = 0; 12 | for (int j = 0; j < N; ++j) { 13 | int ai; 14 | cin >> ai; 15 | prefix += ai; 16 | int optimal = 1 << 30; 17 | for (int k = 0; k <= j; ++k) 18 | optimal = min(optimal, prefix - prefix_sum[k] - min_diff[k]); 19 | min_diff.push_back(optimal); 20 | prefix_sum.push_back(prefix); 21 | } 22 | int last = min_diff.back(); 23 | if (last < 0) 24 | cout << "A"; 25 | else if (last > 0) 26 | cout << "B"; 27 | else 28 | cout << "D"; 29 | cout << '\n'; 30 | } 31 | } -------------------------------------------------------------------------------- /solution/2040/main.py: -------------------------------------------------------------------------------- 1 | def main(): 2 | input = open(0, "rb").readline 3 | N = int(input()) 4 | for _ in range(3): 5 | min_diff = [0] 6 | prefix_sum = [0] 7 | prefix = 0 8 | for ai in map(int, input().split()): 9 | prefix += ai 10 | optimal = 1 << 30 11 | for d, s in zip(min_diff, prefix_sum): 12 | optimal = min(optimal, prefix - s - d) 13 | min_diff.append(optimal) 14 | prefix_sum.append(prefix) 15 | if min_diff[-1] < 0: 16 | print("A") 17 | elif min_diff[-1] > 0: 18 | print("B") 19 | else: 20 | print("D") 21 | 22 | 23 | main() 24 | -------------------------------------------------------------------------------- /solution/2040/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | use std::io::*; 3 | let stdin = read_to_string(stdin().lock()).unwrap(); 4 | let mut tokens = stdin.split_whitespace(); 5 | let mut next = || tokens.next().unwrap(); 6 | let n = next().parse::().unwrap(); 7 | for _ in 0..3 { 8 | let mut min_diff = vec![0]; 9 | let mut prefix_sum = vec![0]; 10 | let mut prefix = 0; 11 | for _ in 0..n { 12 | let ai = next().parse::().unwrap(); 13 | prefix += ai; 14 | let mut optimal = i32::MAX; 15 | for (&d, &s) in min_diff.iter().zip(prefix_sum.iter()) { 16 | optimal = optimal.min(prefix - s - d); 17 | } 18 | min_diff.push(optimal); 19 | prefix_sum.push(prefix); 20 | } 21 | let winner = match *min_diff.last().unwrap() { 22 | ..=-1 => 'A', 23 | 1.. => 'B', 24 | 0 => 'D', 25 | }; 26 | println!("{winner}"); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /solution/24039/Main.java: -------------------------------------------------------------------------------- 1 | import java.io.*; 2 | 3 | public class Main { 4 | static BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in), 1048576); 5 | static StringBuilder stdout = new StringBuilder(); 6 | public static void main(String[] args) throws Exception { 7 | int N = Integer.parseInt(stdin.readLine()); 8 | int prev = 2, cur = 3; 9 | for (int i = 4; prev * cur <= N; ++i) { 10 | boolean is_prime = true; 11 | for (int j = 2; j * j <= i; ++j) { 12 | if (i % j == 0) { 13 | is_prime = false; 14 | break; 15 | } 16 | } 17 | if (is_prime) { 18 | prev = cur; 19 | cur = i; 20 | } 21 | } 22 | stdout.append(prev * cur); 23 | System.out.println(stdout); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /solution/24039/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace std; 3 | int main() { 4 | ios_base::sync_with_stdio(false); 5 | cin.tie(nullptr); 6 | 7 | int N; 8 | cin >> N; 9 | int prev = 2, cur = 3; 10 | for (int i = 4; prev * cur <= N; ++i) { 11 | bool is_prime = true; 12 | for (int j = 2; j * j <= i; ++j) { 13 | if (i % j == 0) { 14 | is_prime = false; 15 | break; 16 | } 17 | } 18 | if (is_prime) { 19 | prev = cur; 20 | cur = i; 21 | } 22 | } 23 | cout << prev * cur; 24 | } 25 | -------------------------------------------------------------------------------- /solution/24039/main.js: -------------------------------------------------------------------------------- 1 | const { readFileSync } = require("fs"); 2 | const token = readFileSync(0, { encoding: "ascii" }) 3 | .split(/\s+/) 4 | [Symbol.iterator](); 5 | const line = []; 6 | 7 | const N = +token.next().value; 8 | let prev = 2, cur = 3; 9 | for (let i = 4; prev * cur <= N; ++i) { 10 | let is_prime = true; 11 | for (let j = 2; j * j <= i; ++j) { 12 | if (i % j == 0) { 13 | is_prime = false; 14 | break; 15 | } 16 | } 17 | if (is_prime) { 18 | prev = cur; 19 | cur = i; 20 | } 21 | } 22 | line.push(prev * cur); 23 | console.log(line.join("\n")); 24 | -------------------------------------------------------------------------------- /solution/24039/main.py: -------------------------------------------------------------------------------- 1 | def main(): 2 | input = open(0, "rb").readline 3 | N = int(input()) 4 | prev, cur, i = 2, 3, 4 5 | while prev * cur <= N: 6 | j = 2 7 | while j * j <= i: 8 | if i % j == 0: 9 | break 10 | j += 1 11 | else: 12 | prev = cur 13 | cur = i 14 | i += 1 15 | print(prev * cur) 16 | 17 | 18 | if __name__ == "__main__": 19 | main() 20 | -------------------------------------------------------------------------------- /solution/27065/Main.java: -------------------------------------------------------------------------------- 1 | import java.io.*; 2 | 3 | public class Main { 4 | static BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in), 1048576); 5 | static StringBuilder stdout = new StringBuilder(); 6 | public static void main(String[] args) throws Exception { 7 | int T = Integer.parseInt(stdin.readLine()); 8 | for (int i = 0; i < T; ++i) { 9 | int n = Integer.parseInt(stdin.readLine()); 10 | boolean ok = Main.isOver(n); 11 | for (int j = 2; j * j <= n; ++j) { 12 | if (n % j == 0) { 13 | if (Main.isOver(j) || Main.isOver(n / j)) { 14 | ok = false; 15 | break; 16 | } 17 | } 18 | } 19 | if (ok) { 20 | stdout.append("Good Bye\n"); 21 | } else { 22 | stdout.append("BOJ 2022\n"); 23 | } 24 | } 25 | System.out.println(stdout); 26 | } 27 | private static boolean isOver(int n) { 28 | int sum = 1; 29 | for (int i = 2; i * i <= n; ++i) { 30 | if (n % i == 0) { 31 | sum += i; 32 | int j = n / i; 33 | if (i != j) { 34 | sum += j; 35 | } 36 | } 37 | } 38 | return sum > n; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /solution/27065/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace std; 3 | 4 | bool is_over(int n) { 5 | int sum = 1; 6 | for (int i = 2; i * i <= n; ++i) { 7 | if (n % i == 0) { 8 | sum += i; 9 | int j = n / i; 10 | if (i != j) { 11 | sum += j; 12 | } 13 | } 14 | } 15 | return sum > n; 16 | } 17 | 18 | int main() { 19 | ios_base::sync_with_stdio(false); 20 | cin.tie(nullptr); 21 | 22 | int T; 23 | cin >> T; 24 | 25 | for (int i = 0; i < T; ++i) { 26 | int n; 27 | cin >> n; 28 | bool ok = is_over(n); 29 | for (int i = 2; i * i <= n; ++i) { 30 | if (n % i == 0) { 31 | if (is_over(i) || is_over(n / i)) { 32 | ok = false; 33 | break; 34 | } 35 | } 36 | } 37 | if (ok) { 38 | cout << "Good Bye\n"; 39 | } else { 40 | cout << "BOJ 2022\n"; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /solution/27065/main.js: -------------------------------------------------------------------------------- 1 | const { readFileSync } = require("fs"); 2 | const token = readFileSync(0, { encoding: "ascii" }) 3 | .split(/\s+/) 4 | [Symbol.iterator](); 5 | const line = []; 6 | 7 | const T = +token.next().value; 8 | for (let i = 0; i < T; ++i) { 9 | const n = +token.next().value; 10 | let ok = is_over(n); 11 | for (let i = 2; i * i <= n; ++i) { 12 | if (n % i === 0) { 13 | if (is_over(i) || is_over(n / i)) { 14 | ok = false; 15 | break; 16 | } 17 | } 18 | } 19 | if (ok) { 20 | line.push("Good Bye"); 21 | } else { 22 | line.push("BOJ 2022"); 23 | } 24 | } 25 | console.log(line.join("\n")); 26 | 27 | function is_over(n) { 28 | let sum = 1; 29 | for (let i = 2; i * i <= n; ++i) { 30 | if (n % i === 0) { 31 | sum += i; 32 | const j = n / i; 33 | if (i !== j) { 34 | sum += j; 35 | } 36 | } 37 | } 38 | return sum > n; 39 | } 40 | -------------------------------------------------------------------------------- /solution/27065/main.py: -------------------------------------------------------------------------------- 1 | def is_over(n): 2 | s = 1 3 | i = 2 4 | while i * i <= n: 5 | if n % i == 0: 6 | s += i 7 | j = n // i 8 | if i != j: 9 | s += j 10 | i += 1 11 | return s > n 12 | 13 | 14 | def main(): 15 | input = open(0, "rb").readline 16 | T = int(input()) 17 | for _ in range(T): 18 | n = int(input()) 19 | ok = is_over(n) 20 | i = 2 21 | while i * i <= n: 22 | if n % i == 0: 23 | if is_over(i) or is_over(n // i): 24 | ok = False 25 | break 26 | i += 1 27 | if ok: 28 | print("Good Bye") 29 | else: 30 | print("BOJ 2022") 31 | 32 | 33 | if __name__ == "__main__": 34 | main() 35 | -------------------------------------------------------------------------------- /solution/27965/Main.java: -------------------------------------------------------------------------------- 1 | import java.io.*; 2 | 3 | public class Main { 4 | static BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in), 1048576); 5 | static StringBuilder stdout = new StringBuilder(); 6 | public static void main(String[] args) throws Exception { 7 | String[] line = stdin.readLine().split(" "); 8 | long N = Integer.parseInt(line[0]); 9 | int K = Integer.parseInt(line[1]); 10 | long remainder = 0, ten = 10; 11 | for (long i = 1; i <= N; ++i) { 12 | if (i == ten) { 13 | ten *= 10; 14 | } 15 | remainder = (remainder * ten + i) % K; 16 | } 17 | stdout.append(remainder); 18 | System.out.println(stdout); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /solution/27965/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace std; 3 | int main() { 4 | ios_base::sync_with_stdio(false); 5 | cin.tie(nullptr); 6 | int N, K; 7 | cin >> N >> K; 8 | long long remainder = 0, ten = 10; 9 | for (int i = 1; i <= N; ++i) { 10 | if (i == ten) { 11 | ten *= 10; 12 | } 13 | remainder = (remainder * ten + i) % K; 14 | } 15 | cout << remainder; 16 | } 17 | -------------------------------------------------------------------------------- /solution/27965/main.py: -------------------------------------------------------------------------------- 1 | def main(): 2 | input = open(0, "rb").readline 3 | N, K = map(int, input().split()) 4 | remainder = 0 5 | ten = 10 6 | for i in range(1, N + 1): 7 | if i == ten: 8 | ten *= 10 9 | remainder = (remainder * ten + i) % K 10 | print(remainder) 11 | 12 | 13 | if __name__ == "__main__": 14 | main() 15 | -------------------------------------------------------------------------------- /solution/28138/Main.java: -------------------------------------------------------------------------------- 1 | import java.io.*; 2 | 3 | public class Main { 4 | static BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in), 1048576); 5 | static StringBuilder stdout = new StringBuilder(); 6 | public static void main(String[] args) throws Exception { 7 | String[] line = stdin.readLine().split(" "); 8 | long N = Long.parseLong(line[0]); 9 | long R = Long.parseLong(line[1]); 10 | long factorize = N - R; 11 | long answer = 0; 12 | for (long i = 1; i * i <= factorize; ++i) { 13 | if (factorize % i == 0) { 14 | if (i > R) { 15 | answer += i; 16 | } 17 | long j = factorize / i; 18 | if (j != i && j > R) { 19 | answer += j; 20 | } 21 | } 22 | } 23 | stdout.append(answer); 24 | System.out.println(stdout); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /solution/28138/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace std; 3 | int main() { 4 | ios_base::sync_with_stdio(false); 5 | cin.tie(nullptr); 6 | long long N, R; 7 | cin >> N >> R; 8 | long long factorize = N - R; 9 | long long sum = 0; 10 | for (long long i = 1; i * i <= factorize; ++i) { 11 | if (factorize % i == 0) { 12 | if (i > R) { 13 | sum += i; 14 | } 15 | long long j = factorize / i; 16 | if (j != i && j > R) { 17 | sum += j; 18 | } 19 | } 20 | } 21 | cout << sum; 22 | } 23 | -------------------------------------------------------------------------------- /solution/28138/main.py: -------------------------------------------------------------------------------- 1 | def main(): 2 | input = open(0, "rb").readline 3 | N, R = map(int, input().split()) 4 | factorize = N - R 5 | answer = 0 6 | i = 1 7 | while i * i <= factorize: 8 | if factorize % i == 0: 9 | if i > R: 10 | answer += i 11 | j = factorize // i 12 | if j != i and j > R: 13 | answer += j 14 | i += 1 15 | print(answer) 16 | 17 | 18 | if __name__ == "__main__": 19 | main() 20 | -------------------------------------------------------------------------------- /solution/30242/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | using namespace std; 4 | bool find_queen(vector &column, vector &slash, 5 | vector &backslash, vector &A, int r) { 6 | int N = A.size(); 7 | if (r == N) { 8 | for (auto &Ai : A) 9 | cout << Ai << ' '; 10 | return true; 11 | } else if (A[r] != 0) { 12 | return find_queen(column, slash, backslash, A, r + 1); 13 | } else { 14 | for (int c = 0; c < N; ++c) { 15 | if (!column[c] && !slash[c + r] && !backslash[c + N - r - 1]) { 16 | column[c] = slash[c + r] = backslash[c + N - r - 1] = true; 17 | A[r] = c + 1; 18 | if (find_queen(column, slash, backslash, A, r + 1)) 19 | return true; 20 | A[r] = 0; 21 | column[c] = slash[c + r] = backslash[c + N - r - 1] = false; 22 | } 23 | } 24 | return false; 25 | } 26 | } 27 | 28 | int main() { 29 | cin.tie(nullptr)->sync_with_stdio(false); 30 | int N; 31 | cin >> N; 32 | vector Q(N); 33 | for (auto &Qi : Q) 34 | cin >> Qi; 35 | vector column(N); 36 | vector slash(2 * N - 1); 37 | vector backslash(2 * N - 1); 38 | for (int r = 0; r < N; ++r) { 39 | if (Q[r] != 0) { 40 | int c = Q[r] - 1; 41 | column[c] = slash[c + r] = backslash[c + N - r - 1] = true; 42 | } 43 | } 44 | if (!find_queen(column, slash, backslash, Q, 0)) 45 | cout << -1; 46 | } -------------------------------------------------------------------------------- /solution/30242/main.py: -------------------------------------------------------------------------------- 1 | def find_queen(column, slash, backslash, A, R): 2 | if R == len(A): 3 | print(*A) 4 | return True 5 | elif A[R] != 0: 6 | return find_queen(column, slash, backslash, A, R + 1) 7 | else: 8 | for C in range(len(A)): 9 | if not column[C] and not slash[C + R] and not backslash[C - R]: 10 | column[C] = slash[C + R] = backslash[C - R] = True 11 | A[R] = C + 1 12 | if find_queen(column, slash, backslash, A, R + 1): 13 | return True 14 | A[R] = 0 15 | column[C] = slash[C + R] = backslash[C - R] = False 16 | return False 17 | 18 | def main(): 19 | N = int(input()) 20 | Q = [*map(int, input().split())] 21 | column = [False] * N 22 | slash = [False] * (2 * N - 1) 23 | backslash = [False] * (2 * N - 1) 24 | for R in range(N): 25 | if Q[R] != 0: 26 | C = Q[R] - 1 27 | column[C] = slash[C + R] = backslash[C - R] = True 28 | if not find_queen(column, slash, backslash, Q, 0): 29 | print(-1) 30 | 31 | main() -------------------------------------------------------------------------------- /solution/30242/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | use std::io::*; 3 | let stdin = read_to_string(stdin().lock()).unwrap(); 4 | let mut tokens = stdin.split_whitespace(); 5 | let mut next = || tokens.next().unwrap(); 6 | let n = next().parse::().unwrap(); 7 | let mut q = vec![]; 8 | for _ in 0..n { 9 | let qi = next().parse::().unwrap(); 10 | q.push(qi); 11 | } 12 | let mut column = vec![false; n]; 13 | let mut slash = vec![false; 2 * n - 1]; 14 | let mut backslash = vec![false; 2 * n - 1]; 15 | for r in 0..n { 16 | if q[r] != 0 { 17 | let c = q[r] - 1; 18 | (column[c], slash[c + r], backslash[c + n - r - 1]) = (true, true, true); 19 | } 20 | } 21 | if !find_queen(&mut column, &mut slash, &mut backslash, &mut q, 0) { 22 | println!("-1"); 23 | } 24 | } 25 | 26 | fn find_queen( 27 | column: &mut [bool], 28 | slash: &mut [bool], 29 | backslash: &mut [bool], 30 | a: &mut [usize], 31 | r: usize, 32 | ) -> bool { 33 | let n = a.len(); 34 | if r == n { 35 | for &ai in a.iter() { 36 | print!("{ai} "); 37 | } 38 | true 39 | } else if a[r] != 0 { 40 | find_queen(column, slash, backslash, a, r + 1) 41 | } else { 42 | for c in 0..n { 43 | if !column[c] && !slash[c + r] && !backslash[c + n - r - 1] { 44 | (column[c], slash[c + r], backslash[c + n - r - 1]) = (true, true, true); 45 | a[r] = c + 1; 46 | if find_queen(column, slash, backslash, a, r + 1) { 47 | return true; 48 | } 49 | a[r] = 0; 50 | (column[c], slash[c + r], backslash[c + n - r - 1]) = (false, false, false); 51 | } 52 | } 53 | false 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /solution/4375/Main.java: -------------------------------------------------------------------------------- 1 | import java.io.*; 2 | 3 | public class Main { 4 | static BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in), 1048576); 5 | static StringBuilder stdout = new StringBuilder(); 6 | public static void main(String[] args) throws Exception { 7 | String line; 8 | while (true) { 9 | line = stdin.readLine(); 10 | if (line == null) break; 11 | int n = Integer.parseInt(line); 12 | int length = 1; 13 | int remainder = 1 % n; 14 | while (remainder != 0) { 15 | remainder = (remainder * 10 + 1) % n; 16 | length++; 17 | } 18 | stdout.append(length).append('\n'); 19 | } 20 | System.out.println(stdout); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /solution/4375/README.md: -------------------------------------------------------------------------------- 1 | # [4375 1](https://acmicpc.net/problem/4375) 2 | 3 | - $111 \dots 11$ 꼴의 수 중 가장 짧은 $n$의 배수를 찾아야 합니다. 4 | - $1, 11, 111, \dots$를 차례로 확인하면서 $n$의 배수인지 확인하면 좋지 않을까요? 5 | - $a_1 = 1$부터 시작해서 $a_{k+1} = 10 a_k + 1$의 식으로 $11, 111, \dots$를 만들 수 있습니다. 6 | - 문제는 이 수가 많이 커질 수 있습니다. 7 | - $x$가 $n$의 배수라는 것은 $x \equiv 0 \pmod n$이라는 것입니다. 8 | - $a_k$를 $n$으로 나눈 나머지만 중요하므로, $a_k$ 대신 $a_k$를 $n$으로 나눈 나머지를 변수에 넣읍시다. 9 | - 처음으로 이 나머지가 $0$이 되는 순간에 반복한 횟수를 출력하면 됩니다. 10 | -------------------------------------------------------------------------------- /solution/4375/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace std; 3 | int main() { 4 | ios_base::sync_with_stdio(false); 5 | cin.tie(nullptr); 6 | int n; 7 | while (cin >> n) { 8 | int length = 1; 9 | int remainder = 1 % n; 10 | while (remainder != 0) { 11 | remainder = (remainder * 10 + 1) % n; 12 | length++; 13 | } 14 | cout << length << '\n'; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /solution/4375/main.js: -------------------------------------------------------------------------------- 1 | const { readFileSync } = require("fs"); 2 | const token = readFileSync(0, { encoding: "ascii" }) 3 | .split(/\s+/) 4 | [Symbol.iterator](); 5 | const line = []; 6 | 7 | while (true) { 8 | const n = Number.parseInt(token.next().value); 9 | if (Number.isNaN(n)) break; 10 | let length = 1; 11 | let remainder = 1 % n; 12 | while (remainder != 0) { 13 | remainder = (remainder * 10 + 1) % n; 14 | length++; 15 | } 16 | line.push(length); 17 | } 18 | console.log(line.join("\n")); 19 | -------------------------------------------------------------------------------- /solution/4375/main.py: -------------------------------------------------------------------------------- 1 | def main(): 2 | input = open(0, "rb").readline 3 | while True: 4 | try: 5 | n = int(input()) 6 | length = 1 7 | remainder = 1 % n 8 | while remainder != 0: 9 | remainder = (remainder * 10 + 1) % n 10 | length += 1 11 | print(length) 12 | except ValueError: 13 | break 14 | 15 | 16 | if __name__ == "__main__": 17 | main() 18 | 19 | -------------------------------------------------------------------------------- /solution/9655/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | using namespace std; 4 | int main() { 5 | cin.tie(nullptr)->sync_with_stdio(false); 6 | int N; 7 | cin >> N; 8 | vector first_win = {false}; 9 | for (int i = 1; i <= N; ++i) { 10 | bool is_win = true; 11 | if (first_win[i - 1]) 12 | is_win = false; 13 | if (i >= 3 && first_win[i - 3]) 14 | is_win = false; 15 | first_win.push_back(is_win); 16 | } 17 | if (first_win[N]) 18 | cout << "SK"; 19 | else 20 | cout << "CY"; 21 | } -------------------------------------------------------------------------------- /solution/9655/main.py: -------------------------------------------------------------------------------- 1 | def main(): 2 | input = open(0, "rb").readline 3 | N = int(input()) 4 | first_win = [False] 5 | for _ in range(1, N + 1): 6 | is_win = True 7 | if first_win[-1]: 8 | is_win = False 9 | if len(first_win) >= 3 and first_win[-3]: 10 | is_win = False 11 | first_win.append(is_win) 12 | print("SK" if first_win[N] else "CY") 13 | 14 | main() -------------------------------------------------------------------------------- /solution/9655/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | use std::io::*; 3 | let stdin = read_to_string(stdin().lock()).unwrap(); 4 | let mut tokens = stdin.split_whitespace(); 5 | let mut next = || tokens.next().unwrap(); 6 | let n = next().parse::().unwrap(); 7 | let mut first_win = vec![false]; 8 | for i in 1..=n { 9 | let mut is_win = true; 10 | if first_win[i - 1] { 11 | is_win = false; 12 | } 13 | if i >= 3 && first_win[i - 3] { 14 | is_win = false; 15 | } 16 | first_win.push(is_win); 17 | } 18 | println!("{}", if first_win[n] { "SK" } else { "CY" }); 19 | } 20 | -------------------------------------------------------------------------------- /solution/9711/Main.java: -------------------------------------------------------------------------------- 1 | import java.io.*; 2 | 3 | public class Main { 4 | static BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in), 1048576); 5 | static StringBuilder stdout = new StringBuilder(); 6 | public static void main(String[] args) throws Exception { 7 | int T = Integer.parseInt(stdin.readLine()); 8 | for (int i = 0; i < T; ++i) { 9 | String[] token = stdin.readLine().split(" "); 10 | int P = Integer.parseInt(token[0]); 11 | long Q = Long.parseLong(token[1]); 12 | long prev = 1, cur = 0; 13 | for (int j = 0; j < P; ++j) { 14 | long next = (prev + cur) % Q; 15 | prev = cur; 16 | cur = next; 17 | } 18 | stdout.append("Case #").append(i + 1).append(": ").append(cur).append('\n'); 19 | } 20 | System.out.println(stdout); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /solution/9711/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace std; 3 | int main() { 4 | ios_base::sync_with_stdio(false); 5 | cin.tie(nullptr); 6 | 7 | int T; 8 | cin >> T; 9 | 10 | for (int i = 0; i < T; ++i) { 11 | int P, Q; 12 | cin >> P >> Q; 13 | long long prev = 1, cur = 0; 14 | for (int j = 0; j < P; ++j) { 15 | long long next = (prev + cur) % Q; 16 | prev = cur; 17 | cur = next; 18 | } 19 | cout << "Case #" << i + 1 << ": " << cur << "\n"; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /solution/9711/main.js: -------------------------------------------------------------------------------- 1 | const { readFileSync } = require("fs"); 2 | const token = readFileSync(0, { encoding: "ascii" }) 3 | .split(/\s+/) 4 | [Symbol.iterator](); 5 | const line = []; 6 | 7 | const T = +token.next().value; 8 | for (let i = 0; i < T; ++i) { 9 | const P = +token.next().value; 10 | const Q = +token.next().value; 11 | let prev = 1, cur = 0; 12 | for (let i = 0; i < P; ++i) { 13 | const next = (prev + cur) % Q; 14 | prev = cur; 15 | cur = next; 16 | } 17 | line.push('Case #' + (i + 1) + ': ' + cur); 18 | } 19 | console.log(line.join("\n")); 20 | -------------------------------------------------------------------------------- /solution/9711/main.py: -------------------------------------------------------------------------------- 1 | def main(): 2 | input = open(0, "rb").readline 3 | T = int(input()) 4 | for i in range(T): 5 | P, Q = map(int, input().split()) 6 | prev = 1 7 | cur = 0 8 | for _ in range(P): 9 | prev, cur = cur, (prev + cur) % Q 10 | print(f"Case #{i + 1}: {cur}") 11 | 12 | 13 | if __name__ == "__main__": 14 | main() 15 | --------------------------------------------------------------------------------