<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>김신건의 로그 - 위키 / algorithm</title><description>&apos;algorithm&apos; 카테고리 위키 페이지 최신 업데이트</description><link>https://shinkeonkim.com/</link><language>ko-KR</language><item><title>UTF-8 인코딩</title><link>https://shinkeonkim.com/wiki/algorithm/string/utf8/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/algorithm/string/utf8/</guid><pubDate>Sat, 18 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;UTF-8&lt;/strong&gt; 은 Unicode code point (0&lt;del&gt;0x10FFFF) 를 1&lt;/del&gt;4 byte 가변 길이로 인코딩하는 방식. 1993년 ISO/IEC 10646 표준의 일부로 도입, 현재 웹에서 가장 널리 쓰이는 문자 인코딩. ASCII (0~0x7F) 는 1 byte 로 동일하게 표현되어 하위 호환성이 완벽.&lt;/p&gt;
&lt;p&gt;UTF-8 의 두 가지 핵심 속성:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;자기 동기화 (Self-synchronizing)&lt;/strong&gt;: 스트림 중간부터 읽어도 다음 문자 시작점을 1 byte 만에 찾을 수 있음.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ASCII 호환&lt;/strong&gt;: 영문/숫자/기호 (U+0000~U+007F) 는 UTF-8 과 ASCII 가 동일한 byte 값. 기존 ASCII 시스템과 완전 호환.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;문제-상황과-동기&quot;&gt;문제 상황과 동기&lt;/h2&gt;
&lt;p&gt;Unicode 는 149,000 개 이상의 문자에 정수 ID(code point) 를 할당했지만, &lt;strong&gt;메모리에 어떻게 저장할 것인가&lt;/strong&gt; 가 별도 문제.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;UTF-32&lt;/strong&gt;: 모든 code point 를 4 byte 로. 간단하지만 메모리 4배 낭비. ASCII 텍스트도 문자당 4 byte.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UTF-16&lt;/strong&gt;: BMP(Basic Multilingual Plane, U+0000~U+FFFF) 는 2 byte, 그 이상은 surrogate pair 로 4 byte. Java/C# 내부 표현. 하지만 ASCII 도 2 byte.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UTF-8&lt;/strong&gt;: ASCII 는 1 byte, 그 외 언어권은 2~4 byte. &lt;strong&gt;웹 페이지의 98%&lt;/strong&gt; 가 UTF-8 사용. 파일 크기 최적 + ASCII 호환.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;핵심 통찰: &lt;em&gt;선두 바이트의 상위 비트 패턴&lt;/em&gt; 으로 총 바이트 수를 알 수 있고, &lt;em&gt;연속 바이트는 항상 10xxxxxx&lt;/em&gt; 형태로 sync 가능.&lt;/p&gt;
&lt;h2 id=&quot;시각화&quot;&gt;시각화&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;인코딩-규칙&quot;&gt;인코딩 규칙&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Input[&quot;Unicode code point (U+XXXX)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Input --&gt; R1{&quot;U+0000 ~ U+007F?&quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    R1 --&gt;|Yes| B1[&quot;1 byte: 0xxxxxxx (ASCII 호환)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    R1 --&gt;|No| R2{&quot;U+0080 ~ U+07FF?&quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    R2 --&gt;|Yes| B2[&quot;2 bytes: 110xxxxx 10xxxxxx&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    R2 --&gt;|No| R3{&quot;U+0800 ~ U+FFFF?&quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    R3 --&gt;|Yes| B3[&quot;3 bytes: 1110xxxx 10xxxxxx 10xxxxxx&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    R3 --&gt;|No| B4[&quot;4 bytes: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;핵심-아이디어&quot;&gt;핵심 아이디어&lt;/h2&gt;
&lt;p&gt;UTF-8 은 &lt;strong&gt;자기 동기화(self-synchronizing)&lt;/strong&gt; 인코딩. 바이트 스트림 중간부터 읽어도 다음 문자 시작점을 찾을 수 있음.&lt;/p&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Code point 범위&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;Byte 1&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;Byte 2&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;Byte 3&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;Byte 4&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;표현 가능 비트&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;U+0000 ~ U+007F&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;&lt;code&gt;0xxxxxxx&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;-&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;-&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;-&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;7 bit&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;U+0080 ~ U+07FF&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;&lt;code&gt;110xxxxx&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;&lt;code&gt;10xxxxxx&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;-&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;-&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;11 bit&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;U+0800 ~ U+FFFF&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;&lt;code&gt;1110xxxx&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;&lt;code&gt;10xxxxxx&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;&lt;code&gt;10xxxxxx&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;-&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;16 bit&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;U+10000 ~ U+10FFFF&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;&lt;code&gt;11110xxx&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;&lt;code&gt;10xxxxxx&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;&lt;code&gt;10xxxxxx&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;&lt;code&gt;10xxxxxx&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;21 bit&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;선두 바이트: 선두의 &lt;code&gt;0&lt;/code&gt; 개수 = 전체 바이트 수 (1바이트는 &lt;code&gt;0&lt;/code&gt; 시작으로 구분).&lt;/li&gt;
&lt;li&gt;연속 바이트: 항상 &lt;code&gt;10&lt;/code&gt; 으로 시작. 선두 바이트와 절대 혼동되지 않음.&lt;/li&gt;
&lt;li&gt;Code point 비트는 &lt;code&gt;x&lt;/code&gt; 자리에 MSB부터 순서대로 채움.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;한글-가-uac00-인코딩-예시&quot;&gt;한글 ‘가’ (U+AC00) 인코딩 예시&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;U+AC00 = 1010 1100 0000 0000 (binary)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;3 byte 범위 (U+0800 ~ U+FFFF):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Byte 1: 1110xxxx  -&gt; 1110 1010 = 0xEA&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Byte 2: 10xxxxxx  -&gt; 10 110000 = 0xB0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Byte 3: 10xxxxxx  -&gt; 10 000000 = 0x80&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;결과: EA B0 80&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;알고리즘&quot;&gt;알고리즘&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;encode(cp):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if cp &amp;#x3C; 0x80:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        output [ cp ]                              # 0xxxxxxx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    else if cp &amp;#x3C; 0x800:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        output [ 0xC0 | (cp &gt;&gt; 6),                  # 110xxxxx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                 0x80 | (cp &amp;#x26; 0x3F) ]               # 10xxxxxx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    else if cp &amp;#x3C; 0x10000:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        output [ 0xE0 | (cp &gt;&gt; 12),                 # 1110xxxx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                 0x80 | ((cp &gt;&gt; 6) &amp;#x26; 0x3F),          # 10xxxxxx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                 0x80 | (cp &amp;#x26; 0x3F) ]                # 10xxxxxx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    else:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        output [ 0xF0 | (cp &gt;&gt; 18),                 # 11110xxx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                 0x80 | ((cp &gt;&gt; 12) &amp;#x26; 0x3F),         # 10xxxxxx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                 0x80 | ((cp &gt;&gt; 6) &amp;#x26; 0x3F),           # 10xxxxxx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                 0x80 | (cp &amp;#x26; 0x3F) ]                 # 10xxxxxx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;decode(bytes):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    n = leading_ones(bytes[0])  # 선두 1 개수 (0이면 1바이트)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if n == 0: return bytes[0]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    cp = bytes[0] &amp;#x26; (0x7F &gt;&gt; n)  # 선두 바이트에서 x bits 추출&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for i = 1..n-1:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        assert (bytes[i] &amp;#x26; 0xC0) == 0x80  # 10xxxxxx 검증&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        cp = (cp &amp;#x3C;&amp;#x3C; 6) | (bytes[i] &amp;#x26; 0x3F)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return cp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;구현&quot;&gt;구현&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
variants={[
{
language: “cpp”,
label: “C++”,
code: &lt;code&gt;// code point -&gt; UTF-8 bytes hex 출력 #include &amp;#x3C;bits/stdc++.h&gt; using namespace std; int main() {     unsigned cp = 0xAC00; // &apos;가&apos;     unsigned char b[4]; int n;     if (cp &amp;#x3C; 0x80) { b[0] = cp; n = 1; }     else if (cp &amp;#x3C; 0x800) { b[0] = 0xC0 | (cp &gt;&gt; 6); b[1] = 0x80 | (cp &amp;#x26; 0x3F); n = 2; }     else if (cp &amp;#x3C; 0x10000) { b[0] = 0xE0 | (cp &gt;&gt; 12); b[1] = 0x80 | ((cp &gt;&gt; 6) &amp;#x26; 0x3F); b[2] = 0x80 | (cp &amp;#x26; 0x3F); n = 3; }     else { b[0] = 0xF0 | (cp &gt;&gt; 18); b[1] = 0x80 | ((cp &gt;&gt; 12) &amp;#x26; 0x3F); b[2] = 0x80 | ((cp &gt;&gt; 6) &amp;#x26; 0x3F); b[3] = 0x80 | (cp &amp;#x26; 0x3F); n = 4; }     printf(&quot;U+%04X -&gt; %d bytes:&quot;, cp, n);     for (int i = 0; i &amp;#x3C; n; i++) printf(&quot; %02X&quot;, b[i]);     printf(&quot;\\n&quot;); }&lt;/code&gt;,
},
{
language: “python”,
label: “Python”,
code: `# chr().encode(‘utf-8’) 로 손쉽게 인코딩
cp = 0xAC00  # ‘가’
encoded = chr(cp).encode(‘utf-8’)
print(f”U+{cp:04X} -&gt; {len(encoded)} bytes:”, ’ ‘.join(f’{b:02X}’ for b in encoded))&lt;/p&gt;
&lt;h1 id=&quot;decode-bytes---code-point&quot;&gt;decode: bytes -&gt; code point&lt;/h1&gt;
&lt;p&gt;decoded = int.from_bytes(encoded, ‘big’)&lt;/p&gt;
&lt;h1 id=&quot;utf-8-decode-규칙으로-복원&quot;&gt;UTF-8 decode 규칙으로 복원&lt;/h1&gt;
&lt;p&gt;n = 0
while decoded &gt;&gt; (7 - n) &amp;#x26; 1: n += 1  # leading 1 count
if n == 0:
result = decoded
else:
mask = (1 &amp;#x3C;&amp;#x3C; (7 - n)) - 1
result = decoded &amp;#x26; mask
# … 실제 decode 는 byte 단위 shift 필요
print(f”decode back: U+{cp:04X}“)&lt;code&gt;,     },     {       language: &quot;java&quot;,       label: &quot;Java&quot;,       code: &lt;/code&gt;import java.nio.charset.StandardCharsets;
public class Main {
public static void main(String[] args) {
int cp = 0xAC00; // ‘가’
String s = new String(new int[]{cp}, 0, 1);
byte[] utf8 = s.getBytes(StandardCharsets.UTF_8);
System.out.printf(“U+%04X -&gt; %d bytes:”, cp, utf8.length);
for (byte b : utf8) System.out.printf(” %02X”, b &amp;#x26; 0xFF);
System.out.println();
}
}&lt;code&gt;,     },   ]}   cases={[     {       label: &quot;Code Point -&gt; UTF-8 bytes&quot;,       input: ``,       output: &lt;/code&gt;U+AC00 -&gt; 3 bytes: EA B0 80`,
},
]}
/&gt;&lt;/p&gt;
&lt;h2 id=&quot;복잡도&quot;&gt;복잡도&lt;/h2&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;항목&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;값&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;인코딩 (code point -&gt; bytes)&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;O(1) (최대 4 byte)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;디코딩 (bytes -&gt; code point)&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;O(1)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;바이트 수 판별&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;O(1) (선두 바이트만 확인)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;검증 (valid UTF-8 여부)&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;O(N) (전체 스트림 순회)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;변형--활용&quot;&gt;변형 / 활용&lt;/h2&gt;
&lt;h3 id=&quot;utf-8-signature-bom&quot;&gt;UTF-8 signature (BOM)&lt;/h3&gt;
&lt;p&gt;일부 Windows 프로그램이 파일 앞에 &lt;code&gt;EF BB BF&lt;/code&gt; (BOM, Byte Order Mark) 를 붙임. Unix/Linux 에서는 불필요하며 오히려 문제.&lt;/p&gt;
&lt;h3 id=&quot;utf-8-vs-utf-16-vs-utf-32&quot;&gt;UTF-8 vs UTF-16 vs UTF-32&lt;/h3&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;항목&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;UTF-8&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;UTF-16&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;UTF-32&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;ASCII 크기&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;1 byte&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;2 byte&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;4 byte&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;CJK (한중일)&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;3 byte&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;2 byte&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;4 byte&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;자기 동기화&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;가능&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;불가 (surrogate 깨짐)&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;가능&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;웹 점유율&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;98%&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;2%&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;0% 미만&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;overlong-encoding&quot;&gt;Overlong encoding&lt;/h3&gt;
&lt;p&gt;code point 를 더 긴 바이트 수로 인코딩하는 것. 예: ASCII ‘A’(0x41) 를 &lt;code&gt;0xC1 0x81&lt;/code&gt; 로. 보안 취약점 (유효성 검사 우회) 이므로 모든 구현이 이를 거부해야 함 (RFC 3629).&lt;/p&gt;
&lt;h3 id=&quot;경쟁-표준-비교&quot;&gt;경쟁 표준 비교&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Web[&quot;웹 / 파일 저장&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Java[&quot;Java / C# 내부&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    DB[&quot;DB (MySQL utf8mb4)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Emoji[&quot;이모지 포함 텍스트&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Web --&gt;|&quot;UTF-8 (사실상 표준)&quot;| UTF8[&quot;UTF-8&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Java --&gt;|&quot;char = 2byte&quot;| UTF16[&quot;UTF-16&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    DB --&gt;|&quot;4byte emoji 지원&quot;| UTF8mb4[&quot;UTF-8 (mb4)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Emoji --&gt;|&quot;U+1F600 = 4byte&quot;| UTF8&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;함정&quot;&gt;함정&lt;/h2&gt;
&lt;h3 id=&quot;1-연속-바이트-검증-누락&quot;&gt;1. 연속 바이트 검증 누락&lt;/h3&gt;
&lt;p&gt;디코더가 &lt;code&gt;10xxxxxx&lt;/code&gt; 검증을 안 하면 임의 바이트를 문자로 오인. UTF-8 validity 체크 필수.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 잘못된 bytes 를 무조건 decode 하면 UnicodeDecodeError 또는 오염된 결과&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;b &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; bytes&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;([&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;0x&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;EA&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;0x&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;FF&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;0x&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;80&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;])  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 두 번째 바이트가 10xxxxxx 아님&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Python: b.decode(&apos;utf-8&apos;) -&gt; UnicodeDecodeError (strict mode)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# b.decode(&apos;utf-8&apos;, errors=&apos;ignore&apos;) -&gt; 조용히 건너뜀&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2-56-byte-sequence-cesu-8--old-utf-8&quot;&gt;2. 5/6 byte sequence (CESU-8 / old UTF-8)&lt;/h3&gt;
&lt;p&gt;초기 UTF-8 은 5~6 byte (0xFC, 0xFD 선두) 를 허용했으나 RFC 3629 에서 폐지. 상호운용성 문제의 원인.&lt;/p&gt;
&lt;h3 id=&quot;3-wtf-8--cesu-8&quot;&gt;3. WTF-8 / CESU-8&lt;/h3&gt;
&lt;p&gt;Oracle / Java 가 쓰는 modified UTF-8 (CESU-8) 은 surrogate pair 를 별도 인코딩. 일반 UTF-8 과 혼동 금지.&lt;/p&gt;
&lt;h3 id=&quot;4-windows-ansi-vs-utf-8&quot;&gt;4. Windows ANSI vs UTF-8&lt;/h3&gt;
&lt;p&gt;Windows 의 &lt;code&gt;MultiByteToWideChar(CP_ACP)&lt;/code&gt; 는 시스템 로케일 인코딩이지 UTF-8 이 아님. &lt;code&gt;CP_UTF8&lt;/code&gt; 명시 필요.&lt;/p&gt;
&lt;h3 id=&quot;5-mysql-utf8-vs-utf8mb4&quot;&gt;5. MySQL utf8 vs utf8mb4&lt;/h3&gt;
&lt;p&gt;MySQL 의 &lt;code&gt;utf8&lt;/code&gt; charset 은 최대 3 byte 만 지원 (이모지 4 byte 저장 불가). 반드시 &lt;code&gt;utf8mb4&lt;/code&gt; 를 사용해야 한다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 잘못 (이모지 저장 실패)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; t&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (col &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;VARCHAR&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;255&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CHARACTER&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; SET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; utf8);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 올바름&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; t&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (col &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;VARCHAR&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;255&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CHARACTER&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; SET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; utf8mb4 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;COLLATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; utf8mb4_unicode_ci);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;6-len-vs-문자-수&quot;&gt;6. len() vs 문자 수&lt;/h3&gt;
&lt;p&gt;Python &lt;code&gt;len(s)&lt;/code&gt; 는 &lt;strong&gt;code point 수&lt;/strong&gt;, &lt;code&gt;len(s.encode(&apos;utf-8&apos;))&lt;/code&gt; 은 &lt;strong&gt;byte 수&lt;/strong&gt;. 한글 1글자는 code point 1개지만 3 byte.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;s &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;한글&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;len&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(s))               &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 2 (code points)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;len&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(s.encode(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;utf-8&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;))) &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 6 (bytes)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;boj-연습-문제&quot;&gt;BOJ 연습 문제&lt;/h2&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;번호&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;제목&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;정답률&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;링크&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 11654&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;아스키 코드&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;71.2%&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/kokoa-lab/boj-problems/tree/main/organize_problems/11600-11699/11654&quot;&gt;kokoa-lab&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 10809&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;알파벳 찾기&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;58.7%&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/kokoa-lab/boj-problems/tree/main/organize_problems/10800-10899/10809&quot;&gt;kokoa-lab&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 11720&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;숫자의 합&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;62.3%&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/kokoa-lab/boj-problems/tree/main/organize_problems/11700-11799/11720&quot;&gt;kokoa-lab&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 10824&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;네 수&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;28.5%&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/kokoa-lab/boj-problems/tree/main/organize_problems/10800-10899/10824&quot;&gt;kokoa-lab&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[String|문자열]]&lt;/li&gt;
&lt;li&gt;[[Regex|정규 표현식]]&lt;/li&gt;
&lt;li&gt;[[Parsing|파싱]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>algorithm</category><category>string</category><category>utf8</category><author>koa (김신건)</author></item><item><title>값 / 좌표 압축 (Coordinate Compression)</title><link>https://shinkeonkim.com/wiki/algorithm/query/coordinate-compression/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/algorithm/query/coordinate-compression/</guid><pubDate>Sat, 18 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;값 / 좌표 압축 (Coordinate Compression)&lt;/strong&gt; 은 큰 범위의 값 (예: 10^9) 을 정렬된 순서를 유지하며 &lt;strong&gt;작은 정수 인덱스&lt;/strong&gt; (0..K-1) 로 매핑하는 기법. K = unique value count.&lt;/p&gt;
&lt;p&gt;이 기법의 핵심 속성:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;순서 보존&lt;/strong&gt;: 원래 값의 대소 관계가 압축 후에도 동일하게 유지.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;가역성&lt;/strong&gt;: &lt;code&gt;uniq[compressed[i]] == original[i]&lt;/code&gt; 로 언제든 복원 가능.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;크기 축소&lt;/strong&gt;: 범위 10^9 → K ≤ N 으로, 희소한 값들을 연속 인덱스로 변환.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;문제-상황과-동기&quot;&gt;문제 상황과 동기&lt;/h2&gt;
&lt;p&gt;값의 범위가 10^9 이고 입력 크기가 10^5 라면, Fenwick tree 나 segment tree 를 값 자체를 인덱스로 사용할 수 없음. 압축하면 희소한 값들을 연속된 인덱스로 바꾸어 &lt;strong&gt;배열 기반 자료구조&lt;/strong&gt;를 사용 가능.&lt;/p&gt;
&lt;p&gt;핵심 통찰: &lt;em&gt;상대 순서만 중요하지 절대값은 중요하지 않다. 값을 정렬하고 중복 제거한 뒤, 각 값의 순위(index)가 곧 압축된 좌표.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;언제-좌표-압축이-필요한가&quot;&gt;언제 좌표 압축이 필요한가?&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Start[&quot;입력 값 범위가 크다 (10^8 이상)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Start --&gt; Q1{&quot;배열/트리에 값을&amp;#x3C;br/&gt;직접 인덱스로 쓸 것?&quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 --&gt;|No| End1[&quot;압축 불필요&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 --&gt;|Yes| Q2{&quot;입력 크기 N&quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q2 --&gt;|&quot;N &amp;#x3C;= 10^7&quot;| End2[&quot;직접 배열 가능&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q2 --&gt;|&quot;N 이 작고 값이 크다&quot;| CC[&quot;좌표 압축 적용&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    CC --&gt; DS[&quot;Fenwick, Segment Tree, BIT, 카운팅 배열 등 사용 가능&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;시각화&quot;&gt;시각화&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;핵심-아이디어&quot;&gt;핵심 아이디어&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1. unique = sort(unique(set(original)))   // 정렬 + 중복 제거&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;2. for each value v in original:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     compressed[v] = lower_bound(unique, v)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     // unique[compressed[v]] == v&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;압축 후에는 &lt;code&gt;unique[compressed[i]]&lt;/code&gt; 로 원래 값을 복원 가능.&lt;/p&gt;
&lt;h2 id=&quot;알고리즘&quot;&gt;알고리즘&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;compress(a[]):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    sorted = sort unique values of a&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for each x in a:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        idx = lower_bound(sorted, x)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        print idx    // 0-based compressed coordinate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 예: a = [-5, 10^9, 0, -5, 42]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// sorted = [-5, 0, 42, 10^9]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// compressed = [0, 3, 1, 0, 2]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;단계별-흐름&quot;&gt;단계별 흐름&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A[&quot;원본 배열\n[-5, 10^9, 0, -5, 42]&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B[&quot;유니크 정렬\n[-5, 0, 42, 10^9]&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C[&quot;이진 탐색 매핑\n[0, 3, 1, 0, 2]&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A --&gt;|&quot;sort + unique&quot;| B&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B --&gt;|&quot;lower_bound&quot;| C&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C --&gt; R[&quot;Fenwick/SegTree 에 사용&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;구현&quot;&gt;구현&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
variants={[
{
language: “cpp”,
label: “C++”,
code: `// Coordinate compression: O(N log N)
#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;&lt;/p&gt;
&lt;p&gt;int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;int n; cin &gt;&gt; n;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;vector&amp;#x3C;int&gt; a(n);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for (auto&amp;#x26; v : a) cin &gt;&gt; v;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 1. Extract unique sorted values&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;vector&amp;#x3C;int&gt; uniq = a;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sort(uniq.begin(), uniq.end());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;uniq.erase(unique(uniq.begin(), uniq.end()), uniq.end());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 2. Map each original value to compressed index&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for (auto&amp;#x26; v : a) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    v = (int)(lower_bound(uniq.begin(), uniq.end(), v)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              - uniq.begin());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    cout &amp;#x3C;&amp;#x3C; v &amp;#x3C;&amp;#x3C; &quot; &quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cout &amp;#x3C;&amp;#x3C; &quot;\\n&quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// Reconstruct original: uniq[compressed[i]]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// for (auto v : a) cout &amp;#x3C;&amp;#x3C; uniq[v] &amp;#x3C;&amp;#x3C; &quot; &quot;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;code&gt;,     },     {       language: &quot;python&quot;,       label: &quot;Python&quot;,       code: &lt;/code&gt;# Coordinate compression: O(N log N)
import sys
input = sys.stdin.readline
from bisect import bisect_left&lt;/p&gt;
&lt;p&gt;n = int(input())
a = list(map(int, input().split()))&lt;/p&gt;
&lt;h1 id=&quot;1-extract-unique-sorted-values&quot;&gt;1. Extract unique sorted values&lt;/h1&gt;
&lt;p&gt;uniq = sorted(set(a))&lt;/p&gt;
&lt;h1 id=&quot;2-map-each-value-to-compressed-index&quot;&gt;2. Map each value to compressed index&lt;/h1&gt;
&lt;p&gt;compressed = [bisect_left(uniq, v) for v in a]
print(*compressed)&lt;/p&gt;
&lt;h1 id=&quot;uniqcompressedi--original-ai&quot;&gt;uniq[compressed[i]] == original a[i]`,&lt;/h1&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  language: &quot;java&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  label: &quot;Java&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  code: `// Coordinate compression: O(N log N)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(
new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine());
int[] a = new int[n];
StringTokenizer st = new StringTokenizer(br.readLine());
for (int i = 0; i &amp;#x3C; n; i++)
a[i] = Integer.parseInt(st.nextToken());&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // 1. Extract unique sorted values&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    int[] sorted = a.clone();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    Arrays.sort(sorted);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    int k = 1;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for (int i = 1; i &amp;#x3C; n; i++)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        if (sorted[i] != sorted[k - 1])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            sorted[k++] = sorted[i];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // 2. Binary search for compression&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    StringBuilder sb = new StringBuilder();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for (int v : a) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        int idx = Arrays.binarySearch(sorted, 0, k, v);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        sb.append(idx).append(&apos; &apos;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    System.out.println(sb);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;code&gt;,     },   ]}   cases={[     {       label: &quot;기본 압축&quot;,       input: &lt;/code&gt;7
-5 1000000000 0 -5 42 999999999 0&lt;code&gt;,       output: &lt;/code&gt;0 4 1 0 2 3 1&lt;code&gt;,     },     {       label: &quot;중복 없음&quot;,       input: &lt;/code&gt;5
100 200 300 400 500&lt;code&gt;,       output: &lt;/code&gt;0 1 2 3 4`,
},
]}
/&gt;&lt;/p&gt;
&lt;h2 id=&quot;복잡도&quot;&gt;복잡도&lt;/h2&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;항목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;값&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;시간&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(N log N)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;공간 (unique 배열)&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(K), K = unique value count&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;압축 크기&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;K ≤ N&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;복원 (index → value)&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(1), &lt;code&gt;uniq[idx]&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;변형--활용&quot;&gt;변형 / 활용&lt;/h2&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;적용처&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;설명&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Fenwick / Segment Tree&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;좌표 압축 후 값 범위가 10^9 → 10^5 로 축소&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Mo’s algorithm&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;값 범위를 압축해 카운팅 배열 사용 가능&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Sweeping&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;x, y 좌표를 각각 압축해 격자로 변환&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;LIS&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;음수/큰 값 좌표를 순위로 변환해 O(N log N) LIS&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;2D 압축&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;x, y 각각 압축해 희소 2D 그리드 구성&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;lis-와-조합-예시&quot;&gt;LIS 와 조합 예시&lt;/h3&gt;
&lt;p&gt;LIS (Longest Increasing Subsequence) 에서 좌표 압축을 자주 활용한다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 값 범위가 크거나 음수를 포함하는 경우&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;a = [100, -5, 200, -5, 50]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 압축 후&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;compressed = [2, 0, 3, 0, 1]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 이제 Fenwick tree 크기 = K = 4 로 LIS 계산 가능&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2d-압축&quot;&gt;2D 압축&lt;/h3&gt;
&lt;p&gt;2D 좌표 문제에서 x, y 를 각각 독립적으로 압축한다. 결과로 (최대 N)×(최대 N) 크기의 2D 배열/트리를 사용 가능.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;points = [(10^9, 10^8), (5, 3), (10^9, 3)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;xs = compress([10^9, 5, 10^9])  -&gt; [1, 0, 1]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ys = compress([10^8, 3, 3])     -&gt; [1, 0, 0]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 결과: (1,1), (0,0), (1,0) -&gt; 3x2 그리드&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;함정&quot;&gt;함정&lt;/h2&gt;
&lt;h3 id=&quot;1-중복-제거-누락&quot;&gt;1. 중복 제거 누락&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;unique()&lt;/code&gt; 또는 &lt;code&gt;set()&lt;/code&gt; 으로 중복을 제거하지 않으면, 같은 값이 여러 인덱스를 가져 Fenwick 트리에서 overflow / 잘못된 결과.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 잘못된 예: sort만 하고 unique 안 함&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;vector&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;int&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; uniq &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; a;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sort&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(uniq.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(), uniq.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// uniq = [-5, -5, 0, 42, 10^9] -- 중복 있음!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// -5 의 lower_bound = 0, -5 의 second occurrence = index 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 같은 값 두 개가 다른 인덱스를 가짐 -&gt; 오류&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2-lower_bound-대신-map-사용&quot;&gt;2. lower_bound 대신 map 사용&lt;/h3&gt;
&lt;p&gt;Python 의 &lt;code&gt;dict&lt;/code&gt; 나 C++ 의 &lt;code&gt;unordered_map&lt;/code&gt; 을 쓰면 O(1) mapping 이 가능하지만, 순서가 필요한 경우 (구간 쿼리, Fenwick) 는 정렬 배열 + binary search 가 필수.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 경우에 따라 dict 도 OK (순서 필요 없을 때)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;rank &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {v: i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i, v &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; enumerate&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;sorted&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(a)))}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;compressed &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [rank[v] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; v &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; a]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 단: range query 에서는 인덱스 순서가 의미를 가져야 하므로 이 방식이 더 명확&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;3-0-based-vs-1-based&quot;&gt;3. 0-based vs 1-based&lt;/h3&gt;
&lt;p&gt;Fenwick tree 는 1-based index 가 필요. 압축 결과가 0-based (lower_bound 기본) 이면 &lt;code&gt;compressed[i] + 1&lt;/code&gt; 사용.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Fenwick tree 와 함께 사용 시&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; idx &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; lower_bound&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(uniq.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(), uniq.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(), v) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; uniq.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;fenwick.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;update&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(idx &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // 1-based!&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;4-쿼리-값도-함께-압축해야-할-때&quot;&gt;4. 쿼리 값도 함께 압축해야 할 때&lt;/h3&gt;
&lt;p&gt;범위 쿼리 &lt;code&gt;[L, R]&lt;/code&gt; 에서 L, R 도 압축된 인덱스 공간으로 변환해야 한다. 원본 배열과 쿼리 값을 합쳐서 압축하거나, L 에는 &lt;code&gt;lower_bound&lt;/code&gt;, R 에는 &lt;code&gt;upper_bound&lt;/code&gt; 를 적절히 사용.&lt;/p&gt;
&lt;h2 id=&quot;boj-연습-문제&quot;&gt;BOJ 연습 문제&lt;/h2&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;번호&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;제목&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;정답률&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;링크&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 18870&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;좌표 압축&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;-&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/kokoa-lab/boj-problems/tree/main/organize_problems/18800-18899/18870&quot;&gt;kokoa-lab&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1015&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;수열 정렬&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;-&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/kokoa-lab/boj-problems/tree/main/organize_problems/1000-1099/1015&quot;&gt;kokoa-lab&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 12015&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;가장 긴 증가하는 부분 수열 2&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;-&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/kokoa-lab/boj-problems/tree/main/organize_problems/12000-12099/12015&quot;&gt;kokoa-lab&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 10815&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;숫자 카드&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;-&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/kokoa-lab/boj-problems/tree/main/organize_problems/10800-10899/10815&quot;&gt;kokoa-lab&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[Binary Search|이분 탐색]]&lt;/li&gt;
&lt;li&gt;[[Fenwick Tree|펜윅 트리]]&lt;/li&gt;
&lt;li&gt;[[Mo|Mo’s 알고리즘]]&lt;/li&gt;
&lt;li&gt;[[Sweeping|스위핑]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>algorithm</category><category>query</category><category>coordinate-compression</category><author>koa (김신건)</author></item><item><title>다각형 넓이 (Polygon Area)</title><link>https://shinkeonkim.com/wiki/algorithm/geometry/polygon-area/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/algorithm/geometry/polygon-area/</guid><pubDate>Sat, 18 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;다각형 넓이 (Polygon Area)&lt;/strong&gt; 는 2D 평면 위 &lt;strong&gt;단순다각형 (non-self-intersecting polygon)&lt;/strong&gt; 의 면적을 &lt;strong&gt;신발끈 공식 (Shoelace Formula)&lt;/strong&gt; 으로 O(N) 에 계산하는 알고리즘.&lt;/p&gt;
&lt;p&gt;신발끈 공식의 핵심 속성:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;단순다각형 범용&lt;/strong&gt;: 볼록(convex) / 오목(concave) 모두 동작.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;방향 무관&lt;/strong&gt;: 시계(CW) / 반시계(CCW) 방향에 관계없이 절대값 취하면 정확.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;부호 있는 넓이&lt;/strong&gt;: 부호로 다각형 방향(CCW: 양수, CW: 음수) 판별 가능.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;정수 좌표 친화적&lt;/strong&gt;: 중간 계산이 모두 정수. 최종에만 2 로 나누면 됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;문제-상황과-동기&quot;&gt;문제 상황과 동기&lt;/h2&gt;
&lt;p&gt;다각형의 넓이는 기하 문제에서 가장 빈번하게 등장하는 연산.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Naive&lt;/strong&gt;: 삼각형 분할 후 각각 넓이 합산. O(N) 이지만 구현이 번거롭고 분할 방식을 결정해야 함.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shoelace&lt;/strong&gt;: 각 변의 기여도를 외적 하나로 계산. O(N) 에 통일된 공식.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;핵심 통찰: &lt;em&gt;다각형의 부호 있는 넓이는 각 변 (i, i+1) 의 외적의 합의 절반&lt;/em&gt;. 즉 &lt;code&gt;0.5 * | sum (x_i * y_{i+1} - x_{i+1} * y_i) |&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;이 공식은 다각형이 볼록인지 오목인지, 시계/반시계 방향인지 관계없이 항상 성립.&lt;/p&gt;
&lt;h3 id=&quot;어디서-쓰이는가&quot;&gt;어디서 쓰이는가?&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    PA[&quot;Polygon Area (Shoelace)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    PA --&gt; C1[&quot;기본 면적 계산&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    PA --&gt; C2[&quot;볼록 껍질 넓이&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    PA --&gt; C3[&quot;다각형 내부 격자점 계산 (Pick 정리)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    PA --&gt; C4[&quot;무게 중심 (Centroid) 계산&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    PA --&gt; C5[&quot;방향 판별 (CCW/CW)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C3 --&gt; Pick[&quot;Pick: A = I + B/2 - 1&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C5 --&gt; CH[&quot;Convex Hull 내 방향 정렬&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;시각화&quot;&gt;시각화&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;핵심-아이디어&quot;&gt;핵심 아이디어&lt;/h2&gt;
&lt;p&gt;다각형의 꼭짓점을 &lt;code&gt;P[0], P[1], ..., P[N-1]&lt;/code&gt; (반시계 또는 시계). 각 변 &lt;code&gt;P[i] -&gt; P[i+1]&lt;/code&gt; 에 대해, &lt;strong&gt;사다리꼴의 부호 있는 넓이&lt;/strong&gt; 를 더한다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;S = 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for i in 0..N-1:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    j = (i + 1) % N&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    S += P[i].x * P[j].y&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    S -= P[j].x * P[i].y&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;area = abs(S) / 2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이론적 근거: 그린 정리 (Green’s theorem) 의 이산 버전. 각 변의 사다리꼴 넓이가 모두 더해지면 내부는 중첩되고 외부는 상쇄됨.&lt;/p&gt;
&lt;p&gt;부호: &lt;code&gt;S &gt; 0&lt;/code&gt; 이면 반시계 방향, &lt;code&gt;S &amp;#x3C; 0&lt;/code&gt; 이면 시계 방향.&lt;/p&gt;
&lt;h3 id=&quot;삼각형-예시로-직관-이해&quot;&gt;삼각형 예시로 직관 이해&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;P0=(0,0), P1=(3,0), P2=(0,4) (직각삼각형)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;S = P0.x*P1.y - P1.x*P0.y   = 0*0 - 3*0   = 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  + P1.x*P2.y - P2.x*P1.y   = 3*4 - 0*0   = 12&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  + P2.x*P0.y - P0.x*P2.y   = 0*0 - 0*4   = 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;S = 12&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;area = |12| / 2 = 6.0  (맞음! 밑변 3 × 높이 4 / 2 = 6)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;알고리즘&quot;&gt;알고리즘&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;polygon_area(P[0..N-1]):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    sum = 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for i = 0 to N-1:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        j = (i + 1) % N&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        sum += P[i].x * P[j].y&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        sum -= P[j].x * P[i].y&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return abs(sum) / 2.0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;구현&quot;&gt;구현&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
variants={[
{
language: “cpp”,
label: “C++”,
code: `// Polygon area (shoelace), O(N)
#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;
using ll = long long;&lt;/p&gt;
&lt;p&gt;struct Point { ll x, y; };&lt;/p&gt;
&lt;p&gt;double polygon_area(vector&lt;point&gt;&amp;#x26; p) {
ll sum = 0;
int n = p.size();
for (int i = 0; i &amp;#x3C; n; i++) {
int j = (i + 1) % n;
sum += p[i].x * p[j].y;
sum -= p[j].x * p[i].y;
}
return abs(sum) / 2.0;
}&lt;/point&gt;&lt;/p&gt;
&lt;p&gt;int main() {
vector&lt;point&gt; poly = {{0,0}, {4,0}, {5,2}, {2,5}, {-1,2}};
cout &amp;#x3C;&amp;#x3C; fixed &amp;#x3C;&amp;#x3C; setprecision(1) &amp;#x3C;&amp;#x3C; polygon_area(poly) &amp;#x3C;&amp;#x3C; “\n”;
vector&lt;point&gt; tri = {{0,0}, {3,0}, {0,4}};
cout &amp;#x3C;&amp;#x3C; fixed &amp;#x3C;&amp;#x3C; setprecision(1) &amp;#x3C;&amp;#x3C; polygon_area(tri) &amp;#x3C;&amp;#x3C; “\n”;
}&lt;code&gt;,     },     {       language: &quot;python&quot;,       label: &quot;Python&quot;,       code: &lt;/code&gt;# Shoelace formula, O(N)
def polygon_area(poly):
n = len(poly)
s = 0
for i in range(n):
j = (i + 1) % n
s += poly[i][0] * poly[j][1]
s -= poly[j][0] * poly[i][1]
return abs(s) / 2.0&lt;/point&gt;&lt;/point&gt;&lt;/p&gt;
&lt;p&gt;poly = [(0,0), (4,0), (5,2), (2,5), (-1,2)]
print(f”{polygon_area(poly):.1f}”)
tri = [(0,0), (3,0), (0,4)]
print(f”{polygon_area(tri):.1f}“)&lt;code&gt;,     },     {       language: &quot;java&quot;,       label: &quot;Java&quot;,       code: &lt;/code&gt;import java.util.*;
public class Main {
static double polygonArea(long[][] p) {
long sum = 0;
int n = p.length;
for (int i = 0; i &amp;#x3C; n; i++) {
int j = (i + 1) % n;
sum += p[i][0] * p[j][1];
sum -= p[j][0] * p[i][1];
}
return Math.abs(sum) / 2.0;
}&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;public static void main(String[] args) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    long[][] poly = {{0,0}, {4,0}, {5,2}, {2,5}, {-1,2}};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    System.out.printf(&quot;%.1f%n&quot;, polygonArea(poly));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    long[][] tri = {{0,0}, {3,0}, {0,4}};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    System.out.printf(&quot;%.1f%n&quot;, polygonArea(tri));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;code&gt;,     },   ]}   cases={[     {       label: &quot;오각형 + 삼각형&quot;,       input: ``,       output: &lt;/code&gt;19.0
6.0`,
},
]}
/&gt;&lt;/p&gt;
&lt;h2 id=&quot;복잡도&quot;&gt;복잡도&lt;/h2&gt;

















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;항목&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;값&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;시간&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;O(N) (N = 꼭짓점 수)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;공간&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;O(1) 추가 (입력 배열 제외)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;변형--활용&quot;&gt;변형 / 활용&lt;/h2&gt;
&lt;h3 id=&quot;1-볼록다각형-여부-방향-일관성&quot;&gt;1. 볼록다각형 여부 (방향 일관성)&lt;/h3&gt;
&lt;p&gt;Shoelace 의 &lt;code&gt;sum&lt;/code&gt; 부호로 다각형이 반시계(양수)인지 시계(음수)인지 판별. 모든 연속 세 점의 CCW 가 방향과 일치하는지 확인.&lt;/p&gt;
&lt;h3 id=&quot;2-다각형의-무게-중심&quot;&gt;2. 다각형의 무게 중심&lt;/h3&gt;
&lt;p&gt;면적으로 가중치를 준 꼭짓점 평균으로 centroid 계산 가능.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cx = sum((x_i + x_{i+1}) * (x_i*y_{i+1} - x_{i+1}*y_i)) / (6 * area)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cy = sum((y_i + y_{i+1}) * (x_i*y_{i+1} - x_{i+1}*y_i)) / (6 * area)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; centroid&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(poly):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; len&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(poly)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    cx &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cy &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    area &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(n):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        j &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        cross &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; poly[i][&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; poly[j][&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; poly[j][&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; poly[i][&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        cx &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (poly[i][&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; poly[j][&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cross&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        cy &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (poly[i][&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; poly[j][&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cross&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        area &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cross&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    area &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cx &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;6&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; area), cy &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;6&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; area)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;3-오목-다각형에서도-동작&quot;&gt;3. 오목 다각형에서도 동작&lt;/h3&gt;
&lt;p&gt;Shoelace 는 내부 영역의 중첩과 상쇄가 자동으로 처리되므로 오목 다각형에서도 올바른 값.&lt;/p&gt;
&lt;h3 id=&quot;4-정수-좌표--long-long&quot;&gt;4. 정수 좌표 + long long&lt;/h3&gt;
&lt;p&gt;좌표가 정수이면 넓이는 &lt;code&gt;.0&lt;/code&gt; 또는 &lt;code&gt;.5&lt;/code&gt;. 정수 배율 결과가 필요하면 &lt;code&gt;abs(sum)&lt;/code&gt; 반환 후 따로 2 로 나누기.&lt;/p&gt;
&lt;h3 id=&quot;5-picks-theorem-과-조합&quot;&gt;5. Pick’s Theorem 과 조합&lt;/h3&gt;
&lt;p&gt;격자점 다각형에서 내부 격자점 수 I 를 구하는 데 Shoelace 를 활용.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;A = I + B/2 - 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;I = A - B/2 + 1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;A = Shoelace 로 계산한 넓이&lt;/li&gt;
&lt;li&gt;B = 경계 위의 격자점 수 (각 변의 gcd 합)&lt;/li&gt;
&lt;li&gt;I = 내부 격자점 수&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;함정&quot;&gt;함정&lt;/h2&gt;
&lt;h3 id=&quot;1-오버플로우&quot;&gt;1. 오버플로우&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;x_i * y_j&lt;/code&gt; 가 &lt;code&gt;10^6 * 10^6 = 10^12&lt;/code&gt;. N=10^5 면 sum 은 &lt;code&gt;10^17&lt;/code&gt; 까지 가능. C++ &lt;code&gt;long long&lt;/code&gt; (9e18) 은 OK, &lt;code&gt;int&lt;/code&gt; 는 무조건 overflow.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 위험: int 로 합산하면 overflow&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sum &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // WRONG: 10^17 은 int 범위 초과&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 올바름&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;long&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; long&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sum &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2-다각형-방향&quot;&gt;2. 다각형 방향&lt;/h3&gt;
&lt;p&gt;입력이 시계(cw)인지 반시계(ccw)인지 모를 수 있음. &lt;code&gt;abs(sum)&lt;/code&gt; 으로 절대값 처리.&lt;/p&gt;
&lt;h3 id=&quot;3-마지막-변&quot;&gt;3. 마지막 변&lt;/h3&gt;
&lt;p&gt;i=N-1 일 때 j=0 으로 돌아오는 것 (&lt;code&gt;(i+1) % N&lt;/code&gt;) 이 중요. 열린 폴리라인이 아닌 닫힌 다각형이어야 함.&lt;/p&gt;
&lt;h3 id=&quot;4-정수-나눗셈&quot;&gt;4. 정수 나눗셈&lt;/h3&gt;
&lt;p&gt;C++ 에서 &lt;code&gt;abs(sum) / 2&lt;/code&gt; 는 정수 나눗셈. &lt;code&gt;2.0&lt;/code&gt; 으로 나누거나 &lt;code&gt;(double)&lt;/code&gt; 캐스팅 필요.&lt;/p&gt;
&lt;h3 id=&quot;5-자기-교차-다각형&quot;&gt;5. 자기 교차 다각형&lt;/h3&gt;
&lt;p&gt;Shoelace 는 &lt;strong&gt;단순다각형 (non-self-intersecting)&lt;/strong&gt; 에서만 올바르다. 모래시계 모양 (자기 교차) 등에서는 내부/외부 영역이 상쇄되어 실제 넓이보다 작게 나온다.&lt;/p&gt;
&lt;h2 id=&quot;boj-연습-문제&quot;&gt;BOJ 연습 문제&lt;/h2&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;번호&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;제목&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;정답률&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;링크&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 2166&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;다각형의 면적&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;39.5%&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/kokoa-lab/boj-problems/tree/main/organize_problems/2100-2199/2166&quot;&gt;kokoa-lab&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 2477&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;참외밭&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;44.5%&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/kokoa-lab/boj-problems/tree/main/organize_problems/2400-2499/2477&quot;&gt;kokoa-lab&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1004&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;어린 왕자&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;33.2%&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/kokoa-lab/boj-problems/tree/main/organize_problems/1000-1099/1004&quot;&gt;kokoa-lab&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1709&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;(관련: 원의 넓이)&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;(수집 안 됨)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/kokoa-lab/boj-problems/tree/main/organize_problems/1700-1799/1709&quot;&gt;kokoa-lab&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[CCW]] (외적의 기초)&lt;/li&gt;
&lt;li&gt;[[Convex Hull]] (볼록 껍질에서도 같은 공식)&lt;/li&gt;
&lt;li&gt;[[Geometry]] (기하 기본)&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>algorithm</category><category>geometry</category><category>polygon-area</category><author>koa (김신건)</author></item><item><title>Binary Search Tree (BST): 정렬된 이진 트리</title><link>https://shinkeonkim.com/wiki/algorithm/data-structure/binary-search-tree/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/algorithm/data-structure/binary-search-tree/</guid><pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Binary Search Tree (BST)&lt;/strong&gt; 는 각 노드가 다음 &lt;strong&gt;BST 불변식&lt;/strong&gt;을 만족하는 이진 트리:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;왼쪽 서브트리의 모든 키 &amp;#x3C; 노드 키&lt;/li&gt;
&lt;li&gt;오른쪽 서브트리의 모든 키 &gt; 노드 키&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;연산&lt;/strong&gt;: 탐색, 삽입, 삭제 모두 &lt;strong&gt;평균 O(log N)&lt;/strong&gt;. 최악은 O(N) (편향 트리).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;핵심 성질&lt;/strong&gt;: 중위 순회 (inorder: 왼 -&gt; 루트 -&gt; 오른) 하면 키가 오름차순 정렬된 순서로 출력된다.&lt;/p&gt;
&lt;h2 id=&quot;문제-상황&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p&gt;정렬 배열:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;탐색 O(log N) (이진 탐색)&lt;/li&gt;
&lt;li&gt;삽입/삭제 O(N) (원소 shift)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;연결 리스트:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;삽입/삭제 O(1) (포인터 조작)&lt;/li&gt;
&lt;li&gt;탐색 O(N) (선형 탐색)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;BST 는 두 자료구조의 트레이드오프를 해결: &lt;strong&gt;탐색/삽입/삭제 모두 평균 O(log N)&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;시각화&quot;&gt;시각화&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A[&quot;5&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B[&quot;3&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C[&quot;7&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    D[&quot;1&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    E[&quot;4&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    F[&quot;6&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    G[&quot;9&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A --&gt; B&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A --&gt; C&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B --&gt; D&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B --&gt; E&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C --&gt; F&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C --&gt; G&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;탐색(4): 루트(5) -&gt; 왼쪽(3) -&gt; 오른쪽(4) 찾음. 비교 3회.&lt;/p&gt;
&lt;p&gt;삽입(8): 루트(5) -&gt; 오른(7) -&gt; 오른(9) -&gt; 9의 왼쪽에 삽입.&lt;/p&gt;
&lt;p&gt;중위 순회: 1, 3, 4, 5, 6, 7, 9 (오름차순).&lt;/p&gt;
&lt;h2 id=&quot;핵심-아이디어&quot;&gt;핵심 아이디어&lt;/h2&gt;
&lt;h3 id=&quot;탐색-search&quot;&gt;탐색 (search)&lt;/h3&gt;
&lt;p&gt;현재 노드와 목표 키를 비교해 재귀:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;search(node, k):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if node is null or node.key == k: return node&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if k &amp;#x3C; node.key: return search(node.left, k)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    else:            return search(node.right, k)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;트리 높이 H 에 비례 = O(H). 균형 트리이면 H = O(log N).&lt;/p&gt;
&lt;h3 id=&quot;삽입-insert&quot;&gt;삽입 (insert)&lt;/h3&gt;
&lt;p&gt;탐색과 동일한 경로로 내려가다 null 을 만나면 노드 생성:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;insert(node, k):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if node is null: return new Node(k)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if k &amp;#x3C; node.key: node.left  = insert(node.left,  k)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    else:            node.right = insert(node.right, k)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return node&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;삭제-delete&quot;&gt;삭제 (delete)&lt;/h3&gt;
&lt;p&gt;삭제 케이스 3가지:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;자식 없음 (leaf)&lt;/strong&gt;: 그냥 제거.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;자식 1개&lt;/strong&gt;: 자식으로 대체.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;자식 2개&lt;/strong&gt;: &lt;strong&gt;inorder successor&lt;/strong&gt; (오른쪽 서브트리의 최솟값) 를 현재 노드로 복사, successor 삭제.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;알고리즘&quot;&gt;알고리즘&lt;/h2&gt;
&lt;h3 id=&quot;전체-구현-c&quot;&gt;전체 구현 (C++)&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Node&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; key;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Node &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;l, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;r;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    Node&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; k&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) : &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(k), &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;l&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;nullptr&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;nullptr&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Node&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; search&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Node&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; r&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; k&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;r &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r-&gt;key &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; k) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; k &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r-&gt;key &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; search&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(r-&gt;l, k) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; search&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(r-&gt;r, k);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Node&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; insert&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Node&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; r&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; k&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;r) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Node&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(k);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (k &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r-&gt;key) r-&gt;l &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; insert&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(r-&gt;l, k);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    else&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (k &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r-&gt;key) r-&gt;r &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; insert&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(r-&gt;r, k);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Node&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; minNode&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Node&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; r&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    while&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (r-&gt;l) r &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r-&gt;l;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Node&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; remove&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Node&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; r&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; k&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;r) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nullptr&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (k &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r-&gt;key) r-&gt;l &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; remove&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(r-&gt;l, k);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    else&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (k &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r-&gt;key) r-&gt;r &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; remove&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(r-&gt;r, k);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    else&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        // 삭제 케이스&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;r-&gt;l) { Node&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; tmp &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r-&gt;r; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;delete&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; tmp; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;r-&gt;r) { Node&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; tmp &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r-&gt;l; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;delete&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; tmp; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        // 자식 2개: inorder successor&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Node&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; succ &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; minNode&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(r-&gt;r);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        r-&gt;key &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; succ-&gt;key;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        r-&gt;r &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; remove&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(r-&gt;r, succ-&gt;key);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;중위-순회&quot;&gt;중위 순회&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; inorder&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Node&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; r&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;vector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; res&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;r) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    inorder&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(r-&gt;l, res);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    res.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;push_back&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(r-&gt;key);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    inorder&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(r-&gt;r, res);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;구현&quot;&gt;구현&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
variants={[
{
language: “cpp”,
label: “C++”,
code: `#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;&lt;/p&gt;
&lt;p&gt;struct Node {
int key;
Node *l, *r;
Node(int k) : key(k), l(nullptr), r(nullptr) {}
};&lt;/p&gt;
&lt;p&gt;Node* insert(Node* r, int k) {
if (!r) return new Node(k);
if (k &amp;#x3C; r-&gt;key) r-&gt;l = insert(r-&gt;l, k);
else if (k &gt; r-&gt;key) r-&gt;r = insert(r-&gt;r, k);
return r;
}&lt;/p&gt;
&lt;p&gt;Node* minNode(Node* r) { while (r-&gt;l) r = r-&gt;l; return r; }&lt;/p&gt;
&lt;p&gt;Node* remove(Node* r, int k) {
if (!r) return nullptr;
if (k &amp;#x3C; r-&gt;key) r-&gt;l = remove(r-&gt;l, k);
else if (k &gt; r-&gt;key) r-&gt;r = remove(r-&gt;r, k);
else {
if (!r-&gt;l) { Node* t = r-&gt;r; delete r; return t; }
if (!r-&gt;r) { Node* t = r-&gt;l; delete r; return t; }
Node* s = minNode(r-&gt;r);
r-&gt;key = s-&gt;key;
r-&gt;r = remove(r-&gt;r, s-&gt;key);
}
return r;
}&lt;/p&gt;
&lt;p&gt;void inorder(Node* r, vector&lt;int&gt;&amp;#x26; v) {
if (!r) return;
inorder(r-&gt;l, v);
v.push_back(r-&gt;key);
inorder(r-&gt;r, v);
}&lt;/int&gt;&lt;/p&gt;
&lt;p&gt;int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;int n; cin &gt;&gt; n;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Node* root = nullptr;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for (int i = 0; i &amp;#x3C; n; i++) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    int x; cin &gt;&gt; x;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    root = insert(root, x);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 삭제&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;int d; cin &gt;&gt; d;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;root = remove(root, d);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 중위 순회 출력&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;vector&amp;#x3C;int&gt; res;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;inorder(root, res);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for (int i = 0; i &amp;#x3C; (int)res.size(); i++) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    cout &amp;#x3C;&amp;#x3C; res[i];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if (i + 1 &amp;#x3C; (int)res.size()) cout &amp;#x3C;&amp;#x3C; &quot; &quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cout &amp;#x3C;&amp;#x3C; &quot;\\n&quot;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;code&gt;,     },     {       language: &quot;python&quot;,       label: &quot;Python&quot;,       code: &lt;/code&gt;import sys
input = sys.stdin.readline&lt;/p&gt;
&lt;p&gt;class Node:
def &lt;strong&gt;init&lt;/strong&gt;(self, key):
self.key = key
self.l = self.r = None&lt;/p&gt;
&lt;p&gt;def insert(node, k):
if node is None:
return Node(k)
if k &amp;#x3C; node.key:
node.l = insert(node.l, k)
elif k &gt; node.key:
node.r = insert(node.r, k)
return node&lt;/p&gt;
&lt;p&gt;def min_node(node):
while node.l:
node = node.l
return node&lt;/p&gt;
&lt;p&gt;def remove(node, k):
if node is None:
return None
if k &amp;#x3C; node.key:
node.l = remove(node.l, k)
elif k &gt; node.key:
node.r = remove(node.r, k)
else:
if node.l is None:
return node.r
if node.r is None:
return node.l
s = min_node(node.r)
node.key = s.key
node.r = remove(node.r, s.key)
return node&lt;/p&gt;
&lt;p&gt;def inorder(node, res):
if node is None:
return
inorder(node.l, res)
res.append(node.key)
inorder(node.r, res)&lt;/p&gt;
&lt;p&gt;n = int(input())
vals = list(map(int, input().split()))
root = None
for v in vals:
root = insert(root, v)
d = int(input())
root = remove(root, d)
res = []
inorder(root, res)
print(*res)&lt;code&gt;,     },   ]}   cases={[     {       label: &quot;삽입 후 삭제&quot;,       input: &lt;/code&gt;6
5 3 7 1 4 9
3&lt;code&gt;,       output: &lt;/code&gt;1 4 5 7 9`,
},
]}
/&gt;&lt;/p&gt;
&lt;h2 id=&quot;복잡도&quot;&gt;복잡도&lt;/h2&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;연산&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;평균&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;최악 (편향)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;탐색&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(log N)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;삽입&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(log N)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;삭제&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(log N)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;중위 순회&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;공간&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;왜-균형이-필요한가&quot;&gt;왜 균형이 필요한가&lt;/h2&gt;
&lt;p&gt;정렬된 입력 &lt;code&gt;1, 2, 3, ..., N&lt;/code&gt; 을 순서대로 삽입하면 &lt;strong&gt;오른쪽으로만 뻗는 일자 트리&lt;/strong&gt; 가 되어 높이 H = N, 모든 연산이 O(N) 로 퇴화.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A1[&quot;1&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A2[&quot;2&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A3[&quot;3&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A4[&quot;4&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A1 --&gt; |&quot;right&quot;| A2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A2 --&gt; |&quot;right&quot;| A3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A3 --&gt; |&quot;right&quot;| A4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이를 해결하는 &lt;strong&gt;균형 BST&lt;/strong&gt;:&lt;/p&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;자료구조&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;균형 전략&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;사용&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;[[avl-tree&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;AVL Tree]]&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;높이 차 &amp;#x3C;= 1, 회전&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Red-Black Tree&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;색 기반 완화 균형&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;C++ STL set/map&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;[[bbst&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Treap]]&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;우선순위 무작위화&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Splay Tree&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;최근 접근 노드 루트로&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;캐시 패턴 편향 시&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;[[order-statistics-tree&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OST]]&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;BST + 서브트리 크기&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;함정&quot;&gt;함정&lt;/h2&gt;
&lt;h3 id=&quot;1-중복-원소-처리&quot;&gt;1. 중복 원소 처리&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;k &amp;#x3C; r-&gt;key&lt;/code&gt; / &lt;code&gt;k &gt; r-&gt;key&lt;/code&gt; 만 처리하면 중복은 삽입 무시. 중복 허용이 필요하면 &lt;code&gt;k &amp;#x3C;= r-&gt;key&lt;/code&gt; 로 왼쪽에 넣거나 카운터 필드 추가.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
중복 원소를 왼/오른쪽에 일관성 없이 넣으면 삭제 시 버그 발생. 방향을 통일해야 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;2-삭제-후-inorder-successor-재귀-삭제&quot;&gt;2. 삭제 후 inorder successor 재귀 삭제&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Node&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; s &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; minNode&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(r-&gt;r);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;r-&gt;key &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; s-&gt;key;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;r-&gt;r &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; remove&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(r-&gt;r, s-&gt;key);&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // s-&gt;key 로 삭제해야 정확&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;s&lt;/code&gt; 포인터를 직접 삭제하면 안 된다. &lt;code&gt;remove&lt;/code&gt; 재귀로 처리해야 구조 유지.&lt;/p&gt;
&lt;h3 id=&quot;3-메모리-누수-c&quot;&gt;3. 메모리 누수 (C++)&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;delete r&lt;/code&gt; 을 leaf 삭제 시점에 해야 한다. 재귀 삭제 시 자식 포인터가 남아 있으면 누수.&lt;/p&gt;
&lt;h3 id=&quot;4-랜덤-입력-가정&quot;&gt;4. 랜덤 입력 가정&lt;/h3&gt;
&lt;p&gt;BST 평균 O(log N) 는 &lt;strong&gt;입력이 랜덤 순서&lt;/strong&gt;일 때만. 정렬/역순 입력이면 O(N). 실전에서는 균형 BST 사용 권장.&lt;/p&gt;
&lt;h2 id=&quot;boj-연습-문제&quot;&gt;BOJ 연습 문제&lt;/h2&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;번호&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;제목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;비고&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 5639&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;이진 검색 트리&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;전위 순회로 BST 복원&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 2104&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;부분배열 고르기&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;BST 응용&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1991&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;트리 순회&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;전위/중위/후위 순회&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[avl-tree|AVL Tree]] (엄격한 균형)&lt;/li&gt;
&lt;li&gt;[[bbst|Treap / BBST]] (균형 BST)&lt;/li&gt;
&lt;li&gt;[[order-statistics-tree|Order Statistics Tree]] (BST + rank/select)&lt;/li&gt;
&lt;li&gt;[[segtree|Segment Tree]] (구간 쿼리)&lt;/li&gt;
&lt;li&gt;[[fenwick-tree|Fenwick Tree]] (prefix sum)&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>algorithm</category><category>data-structure</category><category>tree</category><category>bst</category><author>koa (김신건)</author></item><item><title>Branch and Bound: 분기 한정</title><link>https://shinkeonkim.com/wiki/algorithm/foundation/branch-and-bound/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/algorithm/foundation/branch-and-bound/</guid><pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Branch and Bound (B&amp;#x26;B)&lt;/strong&gt; 는 완전 탐색을 하되, &lt;strong&gt;상한/하한을 계산해 유망하지 않은 서브트리를 가지치기&lt;/strong&gt; 하는 기법. 최적화 문제에 적용.&lt;/p&gt;
&lt;p&gt;백트래킹이 “실행 불가능한 해를 가지치기” 한다면, B&amp;#x26;B 는 &lt;strong&gt;최적 해를 넘지 못하는 경우도 추가 제거&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;문제-상황과-동기&quot;&gt;문제 상황과 동기&lt;/h2&gt;
&lt;h3 id=&quot;왜-필요한가&quot;&gt;왜 필요한가&lt;/h3&gt;
&lt;p&gt;N 도시를 방문하는 최단 경로 ([[tsp|TSP]]) 는 완전 탐색 시 O(N!) 가지. N=20 이면 2.4 × 10^18 가지로 현실적으로 불가능.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Naive&lt;/strong&gt;: 모든 경우 탐색, TLE&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Backtracking&lt;/strong&gt;: 불가능한 해만 제거, 여전히 느림&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;B&amp;#x26;B&lt;/strong&gt;: 현재 best 보다 나쁠 수밖에 없는 경로도 즉시 제거, 실용적&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;핵심 통찰: &lt;em&gt;bound 함수가 낙관적 추정을 잘할수록 가지치기 효과 극대화&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;구성-요소&quot;&gt;구성 요소&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Branching&lt;/strong&gt;: 해 공간을 서브문제로 나누기&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bounding&lt;/strong&gt;: 각 서브문제에서 달성 가능한 최선 추정 (낙관적)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pruning&lt;/strong&gt;: 현재 best 보다 bound 가 나쁘면 해당 가지 제거&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;시각화&quot;&gt;시각화&lt;/h2&gt;
&lt;h3 id=&quot;탐색-트리-구조&quot;&gt;탐색 트리 구조&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Root[&quot;루트: 초기 상태&quot;] --&gt; A[&quot;가지 A&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Root --&gt; B[&quot;가지 B&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Root --&gt; C[&quot;가지 C&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A --&gt; A1[&quot;가지치기 (bound 초과)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A --&gt; A2[&quot;계속 탐색&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B --&gt; B1[&quot;가지치기 (bound 초과)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B --&gt; B2[&quot;가지치기 (bound 초과)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C --&gt; C1[&quot;리프: 해 발견, best 갱신&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A2 --&gt; A21[&quot;계속 탐색&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A2 --&gt; A22[&quot;가지치기 (bound 초과)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    style A1 fill:#ffcccc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    style B1 fill:#ffcccc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    style B2 fill:#ffcccc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    style A22 fill:#ffcccc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    style C1 fill:#ccffcc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;빨간 노드는 가지치기(pruning). 초록 노드가 최적 해.&lt;/p&gt;
&lt;h3 id=&quot;백트래킹-vs-bb&quot;&gt;백트래킹 vs B&amp;#x26;B&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    BT[&quot;백트래킹&quot;] --&gt;|&quot;제약 조건 위반 시 컷&quot;| BT2[&quot;실행 불가능한 해 제거&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    BnB[&quot;B and B&quot;] --&gt;|&quot;bound &gt;= best 시 컷&quot;| BnB2[&quot;최적 불가능한 경우도 제거&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    BnB2 --&gt; Result[&quot;더 강력한 가지치기&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;핵심-아이디어&quot;&gt;핵심 아이디어&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;best = 초기 상한 (greedy 등으로 구함)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;우선순위 큐 Q (bound 기준 best-first)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Q.push(초기 노드)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;while Q 가 비지 않을 때:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    node = Q.pop()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if node.bound &gt;= best:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        continue      # pruning&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if node 가 완성된 해:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        best = min(best, node.cost)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        continue&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for child in branch(node):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        child.bound = compute_bound(child)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        if child.bound &amp;#x3C; best:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            Q.push(child)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;return best&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Key invariant&lt;/strong&gt;: bound 는 &lt;em&gt;실제 최적 해보다 항상 낙관적(optimistic)&lt;/em&gt; 이어야 함. 너무 보수적이면 올바른 해를 가지치기.&lt;/p&gt;
&lt;h2 id=&quot;알고리즘&quot;&gt;알고리즘&lt;/h2&gt;
&lt;h3 id=&quot;tsp-의-bound-계산&quot;&gt;TSP 의 Bound 계산&lt;/h3&gt;
&lt;p&gt;현재까지 이동 비용 + 남은 도시로 가는 하한 추정:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;남은 도시와 현재/시작 도시를 잇는 MST 가중치&lt;/li&gt;
&lt;li&gt;각 도시의 최소 진출 간선 합&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;bound = current_cost&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      + sum(min outgoing edge of remaining cities)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      + MST(remaining cities)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;01-knapsack-의-bound-계산&quot;&gt;0/1 Knapsack 의 Bound 계산&lt;/h3&gt;
&lt;p&gt;남은 아이템을 무게 대비 가치 순으로 정렬, fractional 로 채운 최대 가치:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;bound = current_value&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      + fractional_knapsack(remaining_capacity, remaining_items)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fractional 이므로 정수 해의 상한을 낙관적으로 추정.&lt;/p&gt;
&lt;h2 id=&quot;구현&quot;&gt;구현&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
variants={[
{
language: “cpp”,
label: “C++”,
code: `// 0/1 Knapsack Branch and Bound
#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;&lt;/p&gt;
&lt;p&gt;struct Item { int w, v; };
struct Node {
int level, value, weight;
double bound;
bool operator&amp;#x3C;(const Node&amp;#x26; o) const { return bound &amp;#x3C; o.bound; }
};&lt;/p&gt;
&lt;p&gt;int n, W;
vector&lt;item&gt; items;&lt;/item&gt;&lt;/p&gt;
&lt;p&gt;double compute_bound(const Node&amp;#x26; node) {
if (node.weight &gt;= W) return 0;
double bound = node.value;
int j = node.level + 1;
int totw = node.weight;
while (j &amp;#x3C; n &amp;#x26;&amp;#x26; totw + items[j].w &amp;#x3C;= W) {
totw += items[j].w;
bound += items[j].v;
j++;
}
if (j &amp;#x3C; n)
bound += (double)(W - totw) / items[j].w * items[j].v;
return bound;
}&lt;/p&gt;
&lt;p&gt;int knapsack_bnb() {
sort(items.begin(), items.end(),
[](const Item&amp;#x26; a, const Item&amp;#x26; b) {
return (double)a.v / a.w &gt; (double)b.v / b.w;
});&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;priority_queue&amp;#x3C;Node&gt; pq;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Node root{-1, 0, 0, 0};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;root.bound = compute_bound(root);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;pq.push(root);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;int best = 0;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;while (!pq.empty()) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    Node cur = pq.top(); pq.pop();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if (cur.bound &amp;#x3C;= best) continue;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    Node next;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    next.level = cur.level + 1;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if (next.level &gt;= n) continue;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // 현재 아이템 포함&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    next.weight = cur.weight + items[next.level].w;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    next.value  = cur.value  + items[next.level].v;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if (next.weight &amp;#x3C;= W &amp;#x26;&amp;#x26; next.value &gt; best)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        best = next.value;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    next.bound = compute_bound(next);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if (next.bound &gt; best) pq.push(next);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // 현재 아이템 미포함&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    next.weight = cur.weight;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    next.value  = cur.value;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    next.bound  = compute_bound(next);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if (next.bound &gt; best) pq.push(next);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;return best;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;p&gt;int main() {
n = 4; W = 15;
items = {{2, 10}, {4, 10}, {6, 12}, {9, 18}};
cout &amp;#x3C;&amp;#x3C; knapsack_bnb() &amp;#x3C;&amp;#x3C; “\n”;  // 38
}&lt;code&gt;,     },     {       language: &quot;python&quot;,       label: &quot;Python&quot;,       code: &lt;/code&gt;# 0/1 Knapsack Branch and Bound
import heapq&lt;/p&gt;
&lt;p&gt;def knapsack_bnb(n, W, weights, values):
items = sorted(zip(weights, values),
key=lambda x: -x[1] / x[0])&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;def compute_bound(level, val, wt):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if wt &gt;= W:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        return 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    bound = val&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    j = level + 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    totw = wt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    while j &amp;#x3C; n and totw + items[j][0] &amp;#x3C;= W:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        totw += items[j][0]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        bound += items[j][1]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        j += 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if j &amp;#x3C; n:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        bound += (W - totw) / items[j][0] * items[j][1]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return bound&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;best = 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# (-bound, level, value, weight)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;heap = [(-compute_bound(-1, 0, 0), -1, 0, 0)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;while heap:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    neg_bd, level, val, wt = heapq.heappop(heap)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    bd = -neg_bd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if bd &amp;#x3C;= best:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        continue&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    nlevel = level + 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if nlevel &gt;= n:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        continue&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    w, v = items[nlevel]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    # 포함&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    nwt, nval = wt + w, val + v&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if nwt &amp;#x3C;= W:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        best = max(best, nval)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    nb = compute_bound(nlevel, nval, nwt)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if nb &gt; best:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        heapq.heappush(heap, (-nb, nlevel, nval, nwt))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    # 미포함&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    nb = compute_bound(nlevel, val, wt)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if nb &gt; best:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        heapq.heappush(heap, (-nb, nlevel, val, wt))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;return best&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;n, W = 4, 15
weights = [2, 4, 6, 9]
values  = [10, 10, 12, 18]
print(knapsack_bnb(n, W, weights, values))  # 38`,
},
]}
cases={[
{
label: “기본 배낭 문제”,
input: “n=4, W=15, items={(w=2,v=10),(w=4,v=10),(w=6,v=12),(w=9,v=18)}”,
output: “38”,
},
{
label: “모든 아이템 수용 가능”,
input: “n=2, W=100, items={(w=2,v=10),(w=4,v=10)}”,
output: “20”,
},
]}
/&gt;&lt;/p&gt;
&lt;h2 id=&quot;복잡도&quot;&gt;복잡도&lt;/h2&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;항목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;값&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;최악 시간&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(2^N)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;실전 시간&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;가지치기 효과로 훨씬 빠름&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;공간 (DFS)&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(N) 스택&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;공간 (Best-first)&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(2^N) 최악 큐&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;가지치기 효과는 bound 품질에 의존. 좋은 초기 해를 greedy 로 구하면 효과 배가.&lt;/p&gt;
&lt;h2 id=&quot;dfs-vs-best-first&quot;&gt;DFS vs Best-first&lt;/h2&gt;























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;방식&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;탐색 순서&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;메모리&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;빠른 해 발견&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;DFS B&amp;#x26;B&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;재귀&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(N)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;느림&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Best-first&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;우선순위 큐&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(2^N) 최악&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;빠름&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;메모리 제약 있으면 DFS, 해 품질이 중요하면 best-first.&lt;/p&gt;
&lt;h3 id=&quot;a-와의-관계&quot;&gt;A* 와의 관계&lt;/h3&gt;
&lt;p&gt;[[a-star|A*]] 는 best-first B&amp;#x26;B 에서 각 노드의 비용이 &lt;code&gt;g(n) + h(n)&lt;/code&gt; 인 특수 경우. B&amp;#x26;B 는 더 일반적인 개념.&lt;/p&gt;
&lt;h2 id=&quot;함정&quot;&gt;함정&lt;/h2&gt;
&lt;h3 id=&quot;1-bound-가-낙관적이지-않으면-최적-해-놓침&quot;&gt;1. Bound 가 낙관적이지 않으면 최적 해 놓침&lt;/h3&gt;
&lt;p&gt;bound 함수는 반드시 실제 최적의 하한(최소화) 또는 상한(최대화) 이어야 함. bound 가 실제보다 보수적이면 정답을 가지치기해 WA.&lt;/p&gt;
&lt;h3 id=&quot;2-초기-해가-없으면-가지치기-불가&quot;&gt;2. 초기 해가 없으면 가지치기 불가&lt;/h3&gt;
&lt;p&gt;처음에 greedy 로 좋은 초기 해를 구해야 효율적 pruning 가능. 초기 best = 0 이면 모든 가지 탐색.&lt;/p&gt;
&lt;h3 id=&quot;3-best-first-는-메모리-폭발-위험&quot;&gt;3. Best-first 는 메모리 폭발 위험&lt;/h3&gt;
&lt;p&gt;큐에 노드가 지수적으로 쌓일 수 있음. 반복 심화 B&amp;#x26;B (ID-BnB) 도 대안.&lt;/p&gt;
&lt;h3 id=&quot;4-bound-계산-비용이-과도하면-역효과&quot;&gt;4. Bound 계산 비용이 과도하면 역효과&lt;/h3&gt;
&lt;p&gt;TSP bound 로 MST 계산이 O(N^2) 이면, 노드마다 계산하는 오버헤드가 큼. 간단한 greedy bound 와 trade-off.&lt;/p&gt;
&lt;h3 id=&quot;5-정수-vs-실수-비교&quot;&gt;5. 정수 vs 실수 비교&lt;/h3&gt;
&lt;p&gt;bound 가 실수(double) 인 경우 &lt;code&gt;bound &gt; best&lt;/code&gt; 비교에 부동소수점 오차 주의.&lt;/p&gt;
&lt;h2 id=&quot;boj-연습-문제&quot;&gt;BOJ 연습 문제&lt;/h2&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;번호&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;제목&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;정답률&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;링크&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 2098&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;외판원 순회&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;41.3%&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/kokoa-lab/boj-problems/tree/main/organize_problems/2000-2099/2098&quot;&gt;kokoa-lab&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 9663&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;N-Queens&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;62.3%&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/kokoa-lab/boj-problems/tree/main/organize_problems/9600-9699/9663&quot;&gt;kokoa-lab&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 15649&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;N과 M (1)&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;63.1%&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/kokoa-lab/boj-problems/tree/main/organize_problems/15600-15699/15649&quot;&gt;kokoa-lab&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1987&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;알파벳&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;41.2%&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/kokoa-lab/boj-problems/tree/main/organize_problems/1900-1999/1987&quot;&gt;kokoa-lab&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[brute-force|Brute Force]]&lt;/li&gt;
&lt;li&gt;[[dp-bitfield|DP Bitfield]]&lt;/li&gt;
&lt;li&gt;[[a-star|A*]]&lt;/li&gt;
&lt;li&gt;[[knapsack|Knapsack]]&lt;/li&gt;
&lt;li&gt;[[tsp|TSP]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>algorithm</category><category>foundation</category><category>search</category><category>pruning</category><author>koa (김신건)</author></item><item><title>Brute Force: 완전 탐색</title><link>https://shinkeonkim.com/wiki/algorithm/foundation/brute-force/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/algorithm/foundation/brute-force/</guid><pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Brute Force (완전 탐색)&lt;/strong&gt; 는 문제의 모든 후보 해를 하나씩 검증하는 접근. 최적은 아니지만 &lt;strong&gt;정확&lt;/strong&gt;하고 &lt;strong&gt;명확&lt;/strong&gt;하며, 큰 문제에도 부분 검증 도구로 사용.&lt;/p&gt;
&lt;p&gt;핵심 원칙: &lt;em&gt;모든 가능성을 빠짐없이 탐색하되, 불필요한 탐색은 가지치기로 제거.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;문제-상황&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p&gt;완전 탐색이 유효한 상황:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;N ≤ 20&lt;/strong&gt;: 부분집합 열거 (2^N ≤ 10^6)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;N ≤ 10&lt;/strong&gt;: 순열 열거 (10! ≈ 3.6×10^6)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;N ≤ 8&lt;/strong&gt;: 순열 열거 (8! = 40,320, 매우 빠름)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;N ≤ 1000&lt;/strong&gt;: 이중 루프 (10^6)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;N ≤ 500&lt;/strong&gt;: 삼중 루프 (1.25×10^8, 타이트)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;완전 탐색으로 정답을 구한 뒤, 최적화 알고리즘의 &lt;strong&gt;정답 검증 도구&lt;/strong&gt;로도 활용.&lt;/p&gt;
&lt;h2 id=&quot;시각화&quot;&gt;시각화&lt;/h2&gt;
&lt;p&gt;완전 탐색에서 최적화로의 발전 경로:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    BF[&quot;완전 탐색 (Brute Force)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P[&quot;가지치기 추가 (Pruning)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    BB[&quot;Branch and Bound&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Memo[&quot;메모이제이션 추가&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    DP[&quot;동적 프로그래밍 (DP)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Bit[&quot;비트마스크 인코딩&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    BitmaskDP[&quot;Bitmask DP&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    BF --&gt;|&quot;반복 계산 제거&quot;| Memo&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    BF --&gt;|&quot;탐색 공간 제한&quot;| P&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P --&gt; BB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Memo --&gt; DP&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    BF --&gt;|&quot;집합 상태 인코딩&quot;| Bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Bit --&gt; BitmaskDP&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;핵심-아이디어&quot;&gt;핵심 아이디어&lt;/h2&gt;
&lt;h3 id=&quot;패턴-1-부분집합-열거-2n&quot;&gt;패턴 1: 부분집합 열거 (2^N)&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for mask = 0 to (1&amp;#x3C;&amp;#x3C;N)-1:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    subset = {i : mask &amp;#x26; (1&amp;#x3C;&amp;#x3C;i) != 0}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    check(subset)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;패턴-2-순열-열거-n&quot;&gt;패턴 2: 순열 열거 (N!)&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;arr = [0, 1, ..., N-1]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;do:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    check(arr)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;while next_permutation(arr)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;패턴-3-재귀-백트래킹&quot;&gt;패턴 3: 재귀 백트래킹&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;function backtrack(state, depth):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if depth == N:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        check(state)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for choice in choices:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        if is_valid(state, choice):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            state.add(choice)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            backtrack(state, depth + 1)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            state.remove(choice)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;패턴-4-중첩-루프&quot;&gt;패턴 4: 중첩 루프&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for i = 0 to N-1:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for j = i+1 to N-1:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        check(i, j)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;알고리즘&quot;&gt;알고리즘&lt;/h2&gt;
&lt;h3 id=&quot;가지치기-pruning&quot;&gt;가지치기 (Pruning)&lt;/h3&gt;
&lt;p&gt;현재 부분 해가 최적 해보다 나쁠 수 없을 때 탐색 중단:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;function backtrack(state, depth, current_cost):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if current_cost &gt;= best_cost: return  # 가지치기&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if depth == N:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        best_cost = min(best_cost, current_cost)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for choice in choices:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        backtrack(state + choice, depth + 1, current_cost + cost(choice))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;meet-in-the-middle&quot;&gt;Meet in the Middle&lt;/h3&gt;
&lt;p&gt;N이 40 정도일 때: 절반씩 나눠 2^20 × 2 = 2×10^6 으로 해결.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;left_results = enumerate_all(first_half)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;right_results = enumerate_all(second_half)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sort(right_results)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for each l in left_results:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    binary_search(right_results, target - l)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;구현&quot;&gt;구현&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
variants={[
{
language: “cpp”,
label: “C++”,
code: `#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;&lt;/p&gt;
&lt;p&gt;int n;
vector&lt;int&gt; arr;&lt;/int&gt;&lt;/p&gt;
&lt;p&gt;// 패턴 1: 부분집합 열거
void subset_enumeration() {
for (int mask = 0; mask &amp;#x3C; (1 &amp;#x3C;&amp;#x3C; n); mask++) {
cout &amp;#x3C;&amp;#x3C; ”{ ”;
for (int i = 0; i &amp;#x3C; n; i++)
if (mask &amp;#x26; (1 &amp;#x3C;&amp;#x3C; i))
cout &amp;#x3C;&amp;#x3C; arr[i] &amp;#x3C;&amp;#x3C; ” ”;
cout &amp;#x3C;&amp;#x3C; ”}\n”;
}
}&lt;/p&gt;
&lt;p&gt;// 패턴 2: 순열 열거
void permutation_enumeration() {
sort(arr.begin(), arr.end());
do {
for (int x : arr) cout &amp;#x3C;&amp;#x3C; x &amp;#x3C;&amp;#x3C; ” ”;
cout &amp;#x3C;&amp;#x3C; “\n”;
} while (next_permutation(arr.begin(), arr.end()));
}&lt;/p&gt;
&lt;p&gt;int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cin &gt;&gt; n;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;arr.resize(n);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for (int i = 0; i &amp;#x3C; n; i++) cin &gt;&gt; arr[i];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;int mode;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cin &gt;&gt; mode;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;if (mode == 1) subset_enumeration();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;else permutation_enumeration();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;return 0;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;code&gt;,     },     {       language: &quot;python&quot;,       label: &quot;Python&quot;,       code: &lt;/code&gt;from itertools import permutations&lt;/p&gt;
&lt;p&gt;def subset_enumeration(arr):
n = len(arr)
for mask in range(1 &amp;#x3C;&amp;#x3C; n):
subset = [arr[i] for i in range(n) if mask &gt;&gt; i &amp;#x26; 1]
print(’{’, *subset, ’}’)&lt;/p&gt;
&lt;p&gt;def permutation_enumeration(arr):
for perm in sorted(permutations(arr)):
print(*perm)&lt;/p&gt;
&lt;p&gt;def main():
n = int(input())
arr = list(map(int, input().split()))
mode = int(input())&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;if mode == 1:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    subset_enumeration(arr)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;else:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    permutation_enumeration(arr)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;main()&lt;code&gt;,     },     {       language: &quot;java&quot;,       label: &quot;Java&quot;,       code: &lt;/code&gt;import java.util.*;&lt;/p&gt;
&lt;p&gt;public class Main {
static int n;
static int[] arr;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 부분집합 열거&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;static void subsetEnumeration() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for (int mask = 0; mask &amp;#x3C; (1 &amp;#x3C;&amp;#x3C; n); mask++) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        System.out.print(&quot;{ &quot;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        for (int i = 0; i &amp;#x3C; n; i++)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            if ((mask &amp;#x26; (1 &amp;#x3C;&amp;#x3C; i)) != 0)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                System.out.print(arr[i] + &quot; &quot;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        System.out.println(&quot;}&quot;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 순열 열거 (재귀)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;static void permute(int[] a, int k) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if (k == n) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        for (int x : a) System.out.print(x + &quot; &quot;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        System.out.println();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        return;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for (int i = k; i &amp;#x3C; n; i++) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        int tmp = a[k]; a[k] = a[i]; a[i] = tmp;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        permute(a, k + 1);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        tmp = a[k]; a[k] = a[i]; a[i] = tmp;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;public static void main(String[] args) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    Scanner sc = new Scanner(System.in);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    n = sc.nextInt();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    arr = new int[n];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for (int i = 0; i &amp;#x3C; n; i++) arr[i] = sc.nextInt();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    int mode = sc.nextInt();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if (mode == 1) subsetEnumeration();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    else permute(arr, 0);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;code&gt;,     },   ]}   cases={[     {       label: &quot;N=3 부분집합 열거&quot;,       input: &lt;/code&gt;3
1 2 3
1&lt;code&gt;,       output: &lt;/code&gt;{ }
{ 1 }
{ 2 }
{ 1 2 }
{ 3 }
{ 1 3 }
{ 2 3 }
{ 1 2 3 }&lt;code&gt;,     },     {       label: &quot;N=3 순열 열거&quot;,       input: &lt;/code&gt;3
1 2 3
2&lt;code&gt;,       output: &lt;/code&gt;1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1`,
},
]}
/&gt;&lt;/p&gt;
&lt;h2 id=&quot;복잡도&quot;&gt;복잡도&lt;/h2&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;탐색 유형&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;복잡도&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;N 한계 (10^8 기준)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;부분집합&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(2^N)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;N ≤ 26&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;순열&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(N!)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;N ≤ 12&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;조합 C(N,K)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(C(N,K))&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;상황별&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;이중 루프&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(N²)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;N ≤ 10^4&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;삼중 루프&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(N³)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;N ≤ 500&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Meet in Middle&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(2^(N/2))&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;N ≤ 40&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;최적화로-발전&quot;&gt;최적화로 발전&lt;/h2&gt;
&lt;h3 id=&quot;1-가지치기--branch-and-boundbranch-and-bound&quot;&gt;1. 가지치기 → [[branch-and-bound|Branch and Bound]]&lt;/h3&gt;
&lt;p&gt;현재 부분 해가 최적 해보다 나쁠 수 없을 때 탐색 중단. 최악 복잡도는 동일하지만 실제로 훨씬 빠름.&lt;/p&gt;
&lt;h3 id=&quot;2-메모이제이션--dp&quot;&gt;2. 메모이제이션 → DP&lt;/h3&gt;
&lt;p&gt;같은 부분 문제를 반복 계산하면 메모이제이션으로 DP 변환. 지수 → 다항식 복잡도.&lt;/p&gt;
&lt;h3 id=&quot;3-비트마스크--dp-bitfieldbitmask-dp&quot;&gt;3. 비트마스크 → [[dp-bitfield|Bitmask DP]]&lt;/h3&gt;
&lt;p&gt;집합 상태를 비트마스크로 인코딩하면 O(N!) → O(2^N · N²) (TSP 예시).&lt;/p&gt;
&lt;h3 id=&quot;4-bfs너비-우선-탐색-결합&quot;&gt;4. [[BFS|너비 우선 탐색]] 결합&lt;/h3&gt;
&lt;p&gt;상태 공간 탐색에서 BFS로 최단 경로 탐색.&lt;/p&gt;
&lt;h2 id=&quot;함정&quot;&gt;함정&lt;/h2&gt;
&lt;h3 id=&quot;1-시간-초과-tle&quot;&gt;1. 시간 초과 (TLE)&lt;/h3&gt;
&lt;p&gt;N=20 순열 (20! ≈ 2.4×10^18)은 절대 불가. N 제약 확인 필수.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
완전 탐색 전 반드시 복잡도 계산. N=15 순열 (15! ≈ 1.3×10^12)도 TLE.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;2-중복-탐색&quot;&gt;2. 중복 탐색&lt;/h3&gt;
&lt;p&gt;순열에서 같은 원소가 있으면 중복 제거 필요. &lt;code&gt;next_permutation&lt;/code&gt;은 정렬된 배열에서 시작해야 모든 순열을 정확히 한 번씩 생성.&lt;/p&gt;
&lt;h3 id=&quot;3-가지치기-조건-오류&quot;&gt;3. 가지치기 조건 오류&lt;/h3&gt;
&lt;p&gt;가지치기 조건이 너무 강하면 정답을 놓침. 너무 약하면 효과 없음.&lt;/p&gt;
&lt;h3 id=&quot;4-재귀-깊이-초과&quot;&gt;4. 재귀 깊이 초과&lt;/h3&gt;
&lt;p&gt;Python에서 재귀 깊이 기본 1000. &lt;code&gt;sys.setrecursionlimit(10**6)&lt;/code&gt; 설정 필요.&lt;/p&gt;
&lt;h3 id=&quot;5-인덱스-범위-오류&quot;&gt;5. 인덱스 범위 오류&lt;/h3&gt;
&lt;p&gt;부분집합 열거에서 &lt;code&gt;1 &amp;#x3C;&amp;#x3C; N&lt;/code&gt;이 int 범위를 초과할 수 있음. N=31 이상이면 &lt;code&gt;1LL &amp;#x3C;&amp;#x3C; N&lt;/code&gt; 사용.&lt;/p&gt;
&lt;h2 id=&quot;boj-연습-문제&quot;&gt;BOJ 연습 문제&lt;/h2&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;번호&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;제목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;유형&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1182&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;부분수열의 합&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;부분집합 열거&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 9663&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;N-Queen&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;백트래킹&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 15649&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;N과 M (1)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;순열&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 2309&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;일곱 난쟁이&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;부분집합&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1759&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;암호 만들기&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;조합&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1208&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;부분수열의 합 2&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Meet in Middle&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[branch-and-bound|Branch and Bound]]&lt;/li&gt;
&lt;li&gt;[[dp-bitfield|Bitmask DP]]&lt;/li&gt;
&lt;li&gt;[[BFS|너비 우선 탐색]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>algorithm</category><category>foundation</category><category>search</category><category>backtracking</category><author>koa (김신건)</author></item><item><title>Bubble Sort</title><link>https://shinkeonkim.com/wiki/algorithm/bubble-sort/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/algorithm/bubble-sort/</guid><pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Bubble Sort (버블 정렬)&lt;/strong&gt; 는 인접한 두 원소를 비교해 잘못된 순서면 교환하는 작업을 배열을 통과하며 반복하는 정렬. 한 번의 통과 (pass) 마다 가장 큰 원소가 마지막으로 &lt;em&gt;떠오른다 (bubble up)&lt;/em&gt; 해서 이름이 붙음.&lt;/p&gt;
&lt;p&gt;알고리즘 교육의 단골이지만, &lt;strong&gt;실무에서는 거의 쓰이지 않는다&lt;/strong&gt; (O(n²)). 다만 한 번의 통과로 정렬 완료 여부를 알 수 있다는 점 때문에 &lt;em&gt;거의 정렬된&lt;/em&gt; 입력에 한해 빠를 수 있다.&lt;/p&gt;
&lt;p&gt;전체 비교는 [[정렬 알고리즘]] 참고.&lt;/p&gt;
&lt;h2 id=&quot;시각화&quot;&gt;시각화&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;아래는 한 번의 pass 에서 최댓값이 오른쪽으로 “떠오르는” 흐름:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Init[&quot;i=0, swapped=false&quot;] --&gt; J[&quot;j=0&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    J --&gt; CMP{&quot;arr[j] &gt; arr[j+1]?&quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    CMP --&gt;|&quot;Yes&quot;| SWAP[&quot;swap + swapped=true&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    CMP --&gt;|&quot;No&quot;| NJ[&quot;j++&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    SWAP --&gt; NJ&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    NJ --&gt; JEnd{&quot;j &amp;#x3C; n-i-1?&quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    JEnd --&gt;|&quot;Yes&quot;| CMP&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    JEnd --&gt;|&quot;No&quot;| SC{&quot;swapped?&quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    SC --&gt;|&quot;false&quot;| Done[&quot;조기 종료&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    SC --&gt;|&quot;true&quot;| Next[&quot;i++, 다음 pass&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Next --&gt; Init&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;알고리즘&quot;&gt;알고리즘&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;bubbleSort(arr):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  n = length(arr)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  for i = 0 to n-1:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    swapped = false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for j = 0 to n-i-2:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      if arr[j] &gt; arr[j+1]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        swap(arr[j], arr[j+1])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        swapped = true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if not swapped:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      break  // 이미 정렬됨, 조기 종료&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;핵심-동작&quot;&gt;핵심 동작&lt;/h3&gt;
&lt;p&gt;매 통과마다 가장 큰 값이 &lt;em&gt;끝으로 떠오른다&lt;/em&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[5, 2, 8, 1, 4]   초기&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[2, 5, 8, 1, 4]   5 &gt; 2, swap&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[2, 5, 8, 1, 4]   5 &amp;#x3C; 8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[2, 5, 1, 8, 4]   8 &gt; 1, swap&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[2, 5, 1, 4, 8]   8 &gt; 4, swap   ← 8 이 끝으로 떠올랐다&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;다음 통과는 마지막 원소 제외하고:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[2, 5, 1, 4, |8|]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;n-1 번의 통과 후 정렬 완료. 단, 한 통과에서 교환이 한 번도 없으면 이미 정렬된 것이므로 조기 종료 가능.&lt;/p&gt;
&lt;h2 id=&quot;구현&quot;&gt;구현&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
variants={[
{
language: “cpp”,
label: “C++”,
code: `#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;&lt;/p&gt;
&lt;p&gt;void bubbleSort(vector&lt;int&gt;&amp;#x26; arr) {
int n = arr.size();
for (int i = 0; i &amp;#x3C; n - 1; i++) {
bool swapped = false;
for (int j = 0; j &amp;#x3C; n - i - 1; j++) {
if (arr[j] &gt; arr[j+1]) {
swap(arr[j], arr[j+1]);
swapped = true;
}
}
if (!swapped) break;  // 이미 정렬됨
}
}&lt;/int&gt;&lt;/p&gt;
&lt;p&gt;int main() {
vector&lt;int&gt; arr = {64, 34, 25, 12, 22, 11, 90};
int n = arr.size();
cout &amp;#x3C;&amp;#x3C; “Before: ”;
for (int x : arr) cout &amp;#x3C;&amp;#x3C; x &amp;#x3C;&amp;#x3C; ” ”;
cout &amp;#x3C;&amp;#x3C; “\n”;
bubbleSort(arr);
cout &amp;#x3C;&amp;#x3C; “After:  ”;
for (int x : arr) cout &amp;#x3C;&amp;#x3C; x &amp;#x3C;&amp;#x3C; ” ”;
cout &amp;#x3C;&amp;#x3C; “\n”;
}&lt;code&gt;,     },     {       language: &quot;python&quot;,       label: &quot;Python&quot;,       code: &lt;/code&gt;def bubble_sort(arr: list) -&gt; None:
n = len(arr)
for i in range(n - 1):
swapped = False
for j in range(n - i - 1):
if arr[j] &gt; arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
swapped = True
if not swapped:
break  # 이미 정렬됨&lt;/int&gt;&lt;/p&gt;
&lt;p&gt;arr = [64, 34, 25, 12, 22, 11, 90]
print(“Before:”, arr)
bubble_sort(arr)
print(“After: ”, arr)&lt;code&gt;,     },   ]}   cases={[     {       label: &quot;기본 실행&quot;,       input: &quot;&quot;,       output: &lt;/code&gt;Before: 64 34 25 12 22 11 90
After:  11 12 22 25 34 64 90&lt;code&gt;,     },     {       label: &quot;이미 정렬된 입력 (조기 종료)&quot;,       input: &quot;&quot;,       output: &lt;/code&gt;Before: 1 2 3 4 5
After:  1 2 3 4 5`,
},
]}
/&gt;&lt;/p&gt;
&lt;h2 id=&quot;복잡도&quot;&gt;복잡도&lt;/h2&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;항목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;값&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;설명&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;시간 (최선)&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(n)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;이미 정렬됨 (early exit)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;시간 (평균)&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(n²)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;시간 (최악)&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(n²)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;역순 입력&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;공간&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(1)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;in-place&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;안정성&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓ Stable&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;같은 키는 교환 안 함&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;In-place&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;다른-on-정렬과의-비교&quot;&gt;다른 O(n²) 정렬과의 비교&lt;/h2&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;알고리즘&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;비교 횟수&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;교환 횟수&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;특징&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Bubble&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(n²)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(n²)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;안정. 거의 정렬된 입력 빠름&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;[[Insertion Sort]]&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(n²)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(n²)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;안정. 거의 정렬된 입력 가장 빠름&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;[[Selection Sort]]&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(n²)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;O(n)&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;불안정. &lt;strong&gt;교환 비용 비쌀 때 유리&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
셋 다 O(n²) 지만 &lt;strong&gt;상수항이 다르다&lt;/strong&gt;. 실측 평균:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Insertion Sort &gt; Selection Sort &gt; Bubble Sort (Bubble 이 가장 느림)&lt;/li&gt;
&lt;li&gt;Insertion 이 캐시 친화적이라 가장 빠름&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;변형&quot;&gt;변형&lt;/h2&gt;
&lt;h3 id=&quot;cocktail-shaker-sort&quot;&gt;Cocktail Shaker Sort&lt;/h3&gt;
&lt;p&gt;양방향으로 통과. 작은 값은 앞으로, 큰 값은 뒤로 동시에 이동.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;forward pass:  큰 값을 끝으로&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;backward pass: 작은 값을 앞으로&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;순방향만 하는 Bubble 보다 빨라지는 입력이 있지만 여전히 O(n²).&lt;/p&gt;
&lt;h3 id=&quot;comb-sort&quot;&gt;Comb Sort&lt;/h3&gt;
&lt;p&gt;처음에는 멀리 떨어진 원소끼리 비교 (gap 사용), 점차 gap 을 줄임. Shell Sort 와 비슷한 발상으로 O(n²) 평균 → O(n log n) 평균 가능.&lt;/p&gt;
&lt;h2 id=&quot;왜-가르치는가&quot;&gt;왜 가르치는가&lt;/h2&gt;
&lt;p&gt;실용성보다는 &lt;strong&gt;교육적 가치&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;가장 쉬운 정렬 알고리즘&lt;/strong&gt;, 한눈에 이해&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;swap, in-place, stability 같은 개념의 첫 접점&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;O(n²) 의 직관&lt;/strong&gt;, 두 중첩 루프&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;early termination 같은 최적화 사고&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;거의-정렬된-입력에-빠른-이유&quot;&gt;거의 정렬된 입력에 빠른 이유&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; arr&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;6&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;7&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]; &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 3, 4 만 어긋남&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 1 회 통과: 3, 4 교환 → swapped = true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 2 회 통과: 교환 없음 → swapped = false → 조기 종료&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 총 2n 비교, 1 swap → O(n)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 경우 Insertion Sort 도 마찬가지로 O(n). 둘 다 &lt;em&gt;거의 정렬된&lt;/em&gt; 입력에 강하다.&lt;/p&gt;
&lt;h2 id=&quot;실무-사용-사례-드뭄&quot;&gt;실무 사용 사례 (드뭄)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;임베디드 / 펌웨어&lt;/strong&gt;: 코드 크기가 극단적으로 작아야 할 때 (수십 바이트)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;교육 / 예제 코드&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;테스트용 reference implementation&lt;/strong&gt;, 다른 정렬 알고리즘의 결과 검증&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그 외에는 사실상 사용되지 않는다.&lt;/p&gt;
&lt;h2 id=&quot;함정&quot;&gt;함정&lt;/h2&gt;
&lt;h3 id=&quot;1-내부-루프-범위-오류&quot;&gt;1. 내부 루프 범위 오류&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;j &amp;#x3C; n - i - 1&lt;/code&gt; 에서 &lt;code&gt;-1&lt;/code&gt; 을 빠뜨리면 &lt;code&gt;arr[j+1]&lt;/code&gt; 이 범위를 벗어남. 매 pass 마다 이미 확정된 뒤쪽을 비교할 필요 없음을 명심.&lt;/p&gt;
&lt;h3 id=&quot;2-swapped-플래그-위치&quot;&gt;2. swapped 플래그 위치&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;swapped = false&lt;/code&gt; 를 내부 루프 전에 초기화해야 함. 외부 루프 전에 하면 첫 pass 이후 항상 false 가 되어 조기 종료가 절대 발생하지 않음.&lt;/p&gt;
&lt;h3 id=&quot;3-안정성-보장-조건&quot;&gt;3. 안정성 보장 조건&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;arr[j] &gt; arr[j+1]&lt;/code&gt; 에서 등호 없이 &lt;strong&gt;strict&lt;/strong&gt; 비교를 해야 stable. &lt;code&gt;&gt;=&lt;/code&gt; 로 비교하면 같은 원소도 교환해 unstable 이 됨.&lt;/p&gt;
&lt;h3 id=&quot;4-역순-입력이-최악&quot;&gt;4. 역순 입력이 최악&lt;/h3&gt;
&lt;p&gt;역순 배열: 1번째 pass = n-1 비교, 2번째 pass = n-2 비교 … 총 n*(n-1)/2 비교 + 교환. Insertion Sort 도 비슷하지만 &lt;strong&gt;이동&lt;/strong&gt; 비용이 다름: Bubble 은 인접 swap (O(1) per swap), Insertion 은 shift (O(1) per element, 더 효율적).&lt;/p&gt;
&lt;h3 id=&quot;5-비교-횟수-최소화가-목표가-아닐-때&quot;&gt;5. 비교 횟수 최소화가 목표가 아닐 때&lt;/h3&gt;
&lt;p&gt;교환 비용이 비교 비용보다 훨씬 비싼 경우 (예: 대용량 객체 이동): [[Selection Sort]] 가 교환 횟수 O(n) 으로 더 유리.&lt;/p&gt;
&lt;h2 id=&quot;boj-연습-문제&quot;&gt;BOJ 연습 문제&lt;/h2&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;번호&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;제목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;연관&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1517&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;버블 소트&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Bubble Sort swap 횟수 = 역전쌍 수. Merge Sort 로 O(n log n) 계산&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 2750&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;수 정렬하기&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;정렬 기본 구현 연습&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 25305&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;커트라인&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;정렬 후 k번째 원소 탐색&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
BOJ 1517 “버블 소트” 는 실제로 Bubble Sort 를 구현하는 문제가 &lt;strong&gt;아니다&lt;/strong&gt;. O(n^2) 으로는 TLE. Bubble Sort swap 횟수 = 역전쌍 (inversion pair) 수라는 성질을 활용해 Merge Sort / BIT / Segment Tree 로 O(n log n) 에 계산해야 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[정렬 알고리즘]]&lt;/li&gt;
&lt;li&gt;[[Insertion Sort]]&lt;/li&gt;
&lt;li&gt;[[Selection Sort]]&lt;/li&gt;
&lt;li&gt;[[Quick Sort]]&lt;/li&gt;
&lt;li&gt;Knuth, &lt;em&gt;TAOCP Vol. 3 §5.2.2&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;“Bubble Sort: An Archaeological Algorithmic Analysis” (Owen Astrachan, 2003)&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>algorithm</category><category>sorting</category><category>education</category><author>koa (김신건)</author></item><item><title>Catalan Number: C_n</title><link>https://shinkeonkim.com/wiki/algorithm/math/catalan-number/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/algorithm/math/catalan-number/</guid><pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Catalan number&lt;/strong&gt; 는 조합론에서 자주 등장하는 수열. 닫힌 형식(closed form):&lt;/p&gt;
&lt;p&gt;$$
C_n = \frac{1}{n+1} \binom{2n}{n} = \frac{(2n)!}{(n+1)!, n!}
$$&lt;/p&gt;
&lt;p&gt;초기값: C_0 = 1, C_1 = 1, C_2 = 2, C_3 = 5, C_4 = 14, C_5 = 42, C_6 = 132, C_7 = 429.&lt;/p&gt;
&lt;h2 id=&quot;문제-상황과-동기&quot;&gt;문제 상황과 동기&lt;/h2&gt;
&lt;p&gt;“경우의 수를 구하라” 문제에서 아래 패턴이 보이면 Catalan number 의심:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;n 쌍의 괄호를 올바르게 배치하는 경우의 수&lt;/li&gt;
&lt;li&gt;n+1 개 잎을 가진 완전 이진 트리의 개수&lt;/li&gt;
&lt;li&gt;n+2 각형의 삼각 분할 개수&lt;/li&gt;
&lt;li&gt;격자 경로 중 대각선을 넘지 않는 단조 경로 (Dyck path)&lt;/li&gt;
&lt;li&gt;n 개의 원소를 스택으로 정렬 가능한 순열 수&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;핵심 통찰: &lt;em&gt;구조의 첫 번째 닫힘 지점을 기준으로 좌/우 분할하면 Catalan 점화식이 자연스럽게 등장.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;시각화&quot;&gt;시각화&lt;/h2&gt;
&lt;h3 id=&quot;점화식의-직관&quot;&gt;점화식의 직관&lt;/h3&gt;
&lt;p&gt;n 쌍 괄호에서 맨 첫 &lt;code&gt;(&lt;/code&gt; 와 짝을 이루는 &lt;code&gt;)&lt;/code&gt; 의 위치를 k 로 고정:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Full[&quot;C(n)개 괄호 배열&quot;] --&gt; Split[&quot;첫 닫힘 위치 k로 분할&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Split --&gt; Inner[&quot;안쪽: C(k)가지&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Split --&gt; Outer[&quot;나머지: C(n-1-k)가지&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Inner --&gt; Combine[&quot;모든 k에 대해 곱하여 합산&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Outer --&gt; Combine&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Combine --&gt; Formula[&quot;C(n+1) = 합(C(k) x C(n-k))&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 분해가 Catalan 점화식의 본질:&lt;/p&gt;
&lt;p&gt;$$
C_{n+1} = \sum_{i=0}^{n} C_i \cdot C_{n-i}
$$&lt;/p&gt;
&lt;p&gt;등가 점화식:&lt;/p&gt;
&lt;p&gt;$$
C_n = \frac{2(2n-1)}{n+1} \cdot C_{n-1}
$$&lt;/p&gt;
&lt;h3 id=&quot;이진-트리-재귀-분해&quot;&gt;이진 트리 재귀 분해&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Root[&quot;루트 노드&quot;] --&gt; L[&quot;왼쪽 서브트리: k개 잎&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Root --&gt; R[&quot;오른쪽 서브트리: n-k개 잎&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    L --&gt; Lv[&quot;C(k)가지&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    R --&gt; Rv[&quot;C(n-k)가지&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Lv --&gt; Sum[&quot;C(n) = 합(C(k) x C(n-1-k))&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Rv --&gt; Sum&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;n+1 개 잎 이진 트리를 루트에서 좌/우 분할하면 동일 점화식 유도.&lt;/p&gt;
&lt;h2 id=&quot;응용-모두-c_n-개&quot;&gt;응용 (모두 C_n 개)&lt;/h2&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;구조&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;파라미터&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;개수&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;올바른 괄호 배열&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;n 쌍&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;C_n&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;완전 이진 트리&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;n+1 개 잎&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;C_n&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;볼록 다각형 삼각 분할&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;n+2 각형&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;C_n&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Dyck 경로&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;길이 2n&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;C_n&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;스택 정렬 가능 순열&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;1..n&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;C_n&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;단조 격자 경로 (대각 이하)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;n x n 격자&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;C_n&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;핵심-아이디어&quot;&gt;핵심 아이디어&lt;/h2&gt;
&lt;h3 id=&quot;dp-점화식&quot;&gt;DP 점화식&lt;/h3&gt;
&lt;p&gt;$$
dp[0] = 1
$$
$$
dp[i] = \sum_{k=0}^{i-1} dp[k] \cdot dp[i-1-k]
$$&lt;/p&gt;
&lt;p&gt;시간 O(N^2), 공간 O(N).&lt;/p&gt;
&lt;h3 id=&quot;이항계수로-직접-계산&quot;&gt;이항계수로 직접 계산&lt;/h3&gt;
&lt;p&gt;$$
C_n = \binom{2n}{n} \cdot \frac{1}{n+1}
$$&lt;/p&gt;
&lt;p&gt;모듈러 역원 (p 소수):&lt;/p&gt;
&lt;p&gt;$$
C_n \bmod p = \binom{2n}{n} \cdot (n+1)^{-1} \bmod p
$$&lt;/p&gt;
&lt;h2 id=&quot;알고리즘&quot;&gt;알고리즘&lt;/h2&gt;
&lt;h3 id=&quot;점화식-반복&quot;&gt;점화식 반복&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;dp[0] = 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for i in 1..n:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    dp[i] = 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for k in 0..i-1:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        dp[i] += dp[k] * dp[i-1-k]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;비율-점화식-정수-나눗셈-단조로움-활용&quot;&gt;비율 점화식 (정수 나눗셈, 단조로움 활용)&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;C_0 = 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;C_n = C_{n-1} * 2*(2n-1) / (n+1)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;항상 정수로 나누어 떨어짐 (귀납법으로 증명). 모듈러 없이 큰 수 계산 시 유용.&lt;/p&gt;
&lt;h2 id=&quot;구현&quot;&gt;구현&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
variants={[
{
language: “cpp”,
label: “C++”,
code: `// Catalan number: DP + 이항계수 두 방법
#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;
const long long MOD = 1e9 + 7;&lt;/p&gt;
&lt;p&gt;long long power(long long a, long long b, long long mod) {
long long res = 1; a %= mod;
for (; b; b &gt;&gt;= 1) {
if (b &amp;#x26; 1) res = res * a % mod;
a = a * a % mod;
}
return res;
}&lt;/p&gt;
&lt;p&gt;// 방법 1: DP
vector&lt;long long=&quot;&quot;&gt; catalan_dp(int n) {
vector&lt;long long=&quot;&quot;&gt; dp(n + 1, 0);
dp[0] = 1;
for (int i = 1; i &amp;#x3C;= n; i++)
for (int k = 0; k &amp;#x3C; i; k++)
dp[i] = (dp[i] + dp[k] * dp[i - 1 - k]) % MOD;
return dp;
}&lt;/long&gt;&lt;/long&gt;&lt;/p&gt;
&lt;p&gt;// 방법 2: 이항계수 + 모듈러 역원
long long catalan_binom(int n) {
vector&lt;long long=&quot;&quot;&gt; fact(2 * n + 2);
fact[0] = 1;
for (int i = 1; i &amp;#x3C;= 2 * n + 1; i++) fact[i] = fact[i-1] * i % MOD;
long long binom = fact[2*n] % MOD
* power(fact[n], MOD-2, MOD) % MOD
* power(fact[n], MOD-2, MOD) % MOD;
return binom * power(n + 1, MOD - 2, MOD) % MOD;
}&lt;/long&gt;&lt;/p&gt;
&lt;p&gt;int main() {
auto dp = catalan_dp(10);
for (int i = 0; i &amp;#x3C;= 10; i++)
cout &amp;#x3C;&amp;#x3C; “C(” &amp;#x3C;&amp;#x3C; i &amp;#x3C;&amp;#x3C; ”) = ” &amp;#x3C;&amp;#x3C; dp[i] &amp;#x3C;&amp;#x3C; “\n”;
cout &amp;#x3C;&amp;#x3C; “C(10) via binom = ” &amp;#x3C;&amp;#x3C; catalan_binom(10) &amp;#x3C;&amp;#x3C; “\n”;
}&lt;code&gt;,     },     {       language: &quot;python&quot;,       label: &quot;Python&quot;,       code: &lt;/code&gt;# Catalan number: DP + 비율 점화식
MOD = 10**9 + 7&lt;/p&gt;
&lt;p&gt;def catalan_dp(n):
dp = [0] * (n + 1)
dp[0] = 1
for i in range(1, n + 1):
for k in range(i):
dp[i] = (dp[i] + dp[k] * dp[i - 1 - k]) % MOD
return dp&lt;/p&gt;
&lt;p&gt;def catalan_ratio(n):
&quot;&quot;&quot;C_n = C_{n-1} * 2(2n-1) / (n+1). 항상 정수로 나누어 떨어짐.&quot;&quot;&quot;
c = 1
result = [1]
for i in range(1, n + 1):
c = c * 2 * (2 * i - 1) // (i + 1)
result.append(c % MOD)
return result&lt;/p&gt;
&lt;h1 id=&quot;dp-방식&quot;&gt;DP 방식&lt;/h1&gt;
&lt;p&gt;dp = catalan_dp(10)
for i, v in enumerate(dp):
print(f”C({i}) = {v}“)&lt;/p&gt;
&lt;h1 id=&quot;비율-점화식-모듈러-없이-정확한-정수&quot;&gt;비율 점화식 (모듈러 없이 정확한 정수)&lt;/h1&gt;
&lt;p&gt;rat = catalan_ratio(10)
print(“비율 점화식:”, rat)&lt;code&gt;,     },     {       language: &quot;java&quot;,       label: &quot;Java&quot;,       code: &lt;/code&gt;// Catalan number DP&lt;/p&gt;
&lt;p&gt;public class Main {
static final long MOD = 1_000_000_007L;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;public static void main(String[] args) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    int n = 10;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    long[] dp = new long[n + 1];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    dp[0] = 1;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for (int i = 1; i &amp;#x3C;= n; i++) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        for (int k = 0; k &amp;#x3C; i; k++) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            dp[i] = (dp[i] + dp[k] * dp[i - 1 - k]) % MOD;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for (int i = 0; i &amp;#x3C;= n; i++) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        System.out.println(&quot;C(&quot; + i + &quot;) = &quot; + dp[i]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;code&gt;,     },   ]}   cases={[     {       label: &quot;C(0) ~ C(10)&quot;,       input: &quot;n = 10&quot;,       output: &lt;/code&gt;C(0) = 1
C(1) = 1
C(2) = 2
C(3) = 5
C(4) = 14
C(5) = 42
C(6) = 132
C(7) = 429
C(8) = 1430
C(9) = 4862
C(10) = 16796`,
},
]}
/&gt;&lt;/p&gt;
&lt;h2 id=&quot;복잡도&quot;&gt;복잡도&lt;/h2&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;방법&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;시간&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;공간&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;DP 점화식&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(N^2)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(N)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;이항계수 (팩토리얼 전처리)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(N)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(N)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;비율 점화식&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(N)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(N)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;큰 N 에서 모듈러 연산 필요. C_30 이 이미 long long 범위를 초과.&lt;/p&gt;
&lt;h2 id=&quot;함정&quot;&gt;함정&lt;/h2&gt;
&lt;h3 id=&quot;1-n2각형-삼각분할은-c_n-이지-c_n2-아님&quot;&gt;1. n+2각형 삼각분할은 C_n 이지 C_{n+2} 아님&lt;/h3&gt;
&lt;p&gt;6각형 삼각분할 수 = C_4 = 14. n = (꼭짓점 수) - 2.&lt;/p&gt;
&lt;h3 id=&quot;2-비율-점화식의-모듈러-주의&quot;&gt;2. 비율 점화식의 모듈러 주의&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;C_n = C_{n-1} * 2*(2n-1) / (n+1)&lt;/code&gt; 는 정수지만, 모듈러 연산 후 나눗셈은 역원 필요.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 틀린 방법 (모듈러 후 나눗셈)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;c &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; c &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; MOD&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; //&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# WRONG&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 올바른 방법 1: 모듈러 전에 정수 나눗셈 (정확히 나누어짐)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;c &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; c &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;//&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; MOD&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   # OK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 올바른 방법 2: 모듈러 역원&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;c &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; c &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; MOD&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; pow&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;MOD&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;MOD&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; MOD&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # OK&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;3-스택-정렬-불가능-순열-수&quot;&gt;3. 스택 정렬 불가능 순열 수&lt;/h3&gt;
&lt;p&gt;1..n 을 스택으로 정렬 가능한 순열: C_n 개. 불가능한 순열: n! - C_n 개.&lt;/p&gt;
&lt;h3 id=&quot;4-오버플로우&quot;&gt;4. 오버플로우&lt;/h3&gt;
&lt;p&gt;Python 은 자동 bigint, C++/Java 는 MOD 없이 long long 으로 C_18 부터 오버플로우.&lt;/p&gt;
&lt;h2 id=&quot;boj-연습-문제&quot;&gt;BOJ 연습 문제&lt;/h2&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;번호&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;제목&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;정답률&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;링크&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 10422&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;괄호&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;41.7%&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/kokoa-lab/boj-problems/tree/main/organize_problems/10400-10499/10422&quot;&gt;kokoa-lab&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1720&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;타일 코드&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;45.2%&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/kokoa-lab/boj-problems/tree/main/organize_problems/1700-1799/1720&quot;&gt;kokoa-lab&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 2622&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;삼각형만들기&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;52.3%&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/kokoa-lab/boj-problems/tree/main/organize_problems/2600-2699/2622&quot;&gt;kokoa-lab&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 11722&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;가장 긴 감소하는 부분 수열&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;49.6%&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/kokoa-lab/boj-problems/tree/main/organize_problems/11700-11799/11722&quot;&gt;kokoa-lab&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[combinatorics|조합론]]&lt;/li&gt;
&lt;li&gt;[[pascal-triangle|Pascal Triangle]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>algorithm</category><category>math</category><category>combinatorics</category><category>catalan</category><author>koa (김신건)</author></item><item><title>Convex Hull 3D: 3차원 볼록 껍질</title><link>https://shinkeonkim.com/wiki/algorithm/geometry/convex-hull-3d/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/algorithm/geometry/convex-hull-3d/</guid><pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;3D 점집합의 &lt;strong&gt;볼록 껍질 (Convex Hull)&lt;/strong&gt;: 모든 점을 포함하는 최소 볼록 다면체.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;면 (face) 들이 삼각형으로 구성된 다면체&lt;/li&gt;
&lt;li&gt;각 면의 법선 벡터 (outward normal) 가 외부를 향함&lt;/li&gt;
&lt;li&gt;다면체 내부의 어느 두 점을 이어도 선분이 다면체 안에 존재&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;2D 볼록 껍질의 3D 확장. 2D 에서 “볼록 다각형”이면, 3D 에서 “볼록 다면체”.&lt;/p&gt;
&lt;h2 id=&quot;문제-상황&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p&gt;3D 점집합이 주어질 때 볼록 껍질을 구해야 하는 상황:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;3D 물체의 최소 감싸는 다면체 구하기&lt;/li&gt;
&lt;li&gt;두 점집합 사이 최소 거리 (GJK 알고리즘의 기반)&lt;/li&gt;
&lt;li&gt;Delaunay Triangulation, Voronoi Diagram 계산&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2D 와의 차이&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2D: 볼록 껍질 = 일련의 선분 (edges)&lt;/li&gt;
&lt;li&gt;3D: 볼록 껍질 = 삼각형 면들의 집합 (triangulated surface)&lt;/li&gt;
&lt;li&gt;면의 방향 (orientation) 을 올바르게 유지해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;시각화&quot;&gt;시각화&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Input[&quot;3D 점집합&quot;] --&gt; Init[&quot;초기 사면체\n일반 위치 4점으로 시작&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Init --&gt; Loop[&quot;점 P 추가 반복&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Loop --&gt; Vis[&quot;P 에서 보이는 face 찾기\n법선 벡터 내적 테스트&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Vis --&gt; Del[&quot;보이는 face 제거&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Del --&gt; Horizon[&quot;horizon edge 추출&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Horizon --&gt; NewFace[&quot;P 와 horizon 으로\n새 face 생성&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    NewFace --&gt; Loop&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Loop --&gt; Done[&quot;모든 점 처리 완료&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;핵심-아이디어&quot;&gt;핵심 아이디어&lt;/h2&gt;
&lt;h3 id=&quot;핵심-연산-외적-cross-product&quot;&gt;핵심 연산: 외적 (Cross Product)&lt;/h3&gt;
&lt;p&gt;두 벡터 $\mathbf{a} = (a_x, a_y, a_z)$, $\mathbf{b} = (b_x, b_y, b_z)$ 의 외적:&lt;/p&gt;
&lt;p&gt;$$
\mathbf{a} \times \mathbf{b} = (a_y b_z - a_z b_y, ; a_z b_x - a_x b_z, ; a_x b_y - a_y b_x)
$$&lt;/p&gt;
&lt;p&gt;삼각형 면 ABC 의 outward normal $\mathbf{n} = \overrightarrow{AB} \times \overrightarrow{AC}$.&lt;/p&gt;
&lt;h3 id=&quot;점-p-의-face-가시성-판단&quot;&gt;점 P 의 face 가시성 판단&lt;/h3&gt;
&lt;p&gt;점 P 에서 면 F (정점 A, B, C, outward normal $\mathbf{n}$) 가 보이는가?&lt;/p&gt;
&lt;p&gt;$$
(\mathbf{P} - \mathbf{A}) \cdot \mathbf{n} &gt; 0 \implies \text{보임 (visible)}
$$&lt;/p&gt;
&lt;p&gt;보이는 면은 새 점을 추가할 때 제거 대상.&lt;/p&gt;
&lt;h3 id=&quot;horizon-edge&quot;&gt;Horizon Edge&lt;/h3&gt;
&lt;p&gt;가시 면과 불가시 면 사이의 경계 에지. 새 점 P 에서 이 에지들을 이어 새 삼각형 팬 (fan) 을 형성.&lt;/p&gt;
&lt;h3 id=&quot;일반-위치-general-position&quot;&gt;일반 위치 (General Position)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;4점 이상이 같은 평면에 없을 것&lt;/li&gt;
&lt;li&gt;3점 이상이 같은 직선에 없을 것&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;일반 위치 가정이 없으면 퇴화 케이스 (degenerate case) 처리가 복잡해짐.&lt;/p&gt;
&lt;h2 id=&quot;알고리즘&quot;&gt;알고리즘&lt;/h2&gt;
&lt;h3 id=&quot;incremental-construction&quot;&gt;Incremental Construction&lt;/h3&gt;
&lt;p&gt;한 번에 한 점씩 추가:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;convex_hull_3d(points):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    shuffle(points)  // 무작위 셔플로 O(N log N) 기대 시간&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    CH = tetrahedron(points[0..3])  // 초기 사면체&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for P in points[4:]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        visible_faces = [f for f in CH.faces if is_visible(P, f)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        if not visible_faces:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            continue  // P 가 이미 CH 내부&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        horizon = get_horizon_edges(visible_faces)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        remove visible_faces from CH&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        for edge (A, B) in horizon:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            CH.add_face(A, B, P)  // 새 삼각형&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return CH&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;O(N^2) worst case&lt;/li&gt;
&lt;li&gt;O(N log N) expected (무작위 셔플 후)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;gift-wrapping-3d&quot;&gt;Gift Wrapping (3D)&lt;/h3&gt;
&lt;p&gt;2D gift wrapping 의 3D 확장. 각 에지에서 다음 면을 “감싸서” 찾음.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gift_wrapping_3d(points):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    start_edge = find_initial_edge()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    queue = [start_edge]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    visited_edges = set()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    while queue:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        e = queue.pop()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        if e in visited_edges: continue&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        P = find_visible_point(e, points)  // e 왼쪽에서 가장 왼쪽 점&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        new_face = (e.A, e.B, P)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        add new_face&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        queue += [e.B, P], [P, e.A]  // 새 에지&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        visited_edges.add(e)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;O(N * F), F = 최종 면 개수. H = O(N) 최악 케이스.&lt;/p&gt;
&lt;h3 id=&quot;chans-algorithm&quot;&gt;Chan’s Algorithm&lt;/h3&gt;
&lt;p&gt;O(N log H), H = 볼록 껍질 면 수. 이론적 최적.&lt;/p&gt;
&lt;h2 id=&quot;구현&quot;&gt;구현&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
variants={[
{
language: “cpp”,
label: “C++: 핵심 연산”,
code: `#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;
typedef long long ll;
struct P3 { ll x, y, z; };&lt;/p&gt;
&lt;p&gt;P3 sub(P3 a, P3 b) { return {a.x-b.x, a.y-b.y, a.z-b.z}; }
P3 cross(P3 a, P3 b) {
return {a.y&lt;em&gt;b.z-a.z&lt;/em&gt;b.y, a.z&lt;em&gt;b.x-a.x&lt;/em&gt;b.z, a.x&lt;em&gt;b.y-a.y&lt;/em&gt;b.x};
}
ll dot(P3 a, P3 b) { return a.x&lt;em&gt;b.x + a.y&lt;/em&gt;b.y + a.z*b.z; }&lt;/p&gt;
&lt;p&gt;// 삼각형 face ABC 에서 점 P 가 보이는지 (outward normal 기준)
// normal = (B-A) x (C-A)
bool visible(P3 A, P3 B, P3 C, P3 P) {
P3 n = cross(sub(B, A), sub(C, A));
return dot(n, sub(P, A)) &gt; 0;
}&lt;/p&gt;
&lt;p&gt;int main() {
int n; cin &gt;&gt; n;
vector&lt;p3&gt; pts(n);
for (auto&amp;#x26; p : pts) cin &gt;&gt; p.x &gt;&gt; p.y &gt;&gt; p.z;&lt;/p3&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 가시성 테스트 예시&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;P3 A={0,0,0}, B={1,0,0}, C={0,1,0}, Q={0,0,1};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cout &amp;#x3C;&amp;#x3C; &quot;Q visible from ABC: &quot; &amp;#x3C;&amp;#x3C; visible(A,B,C,Q) &amp;#x3C;&amp;#x3C; &quot;\\n&quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 외적 예시&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;P3 ab = sub(B, A), ac = sub(C, A);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;P3 n = cross(ab, ac);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cout &amp;#x3C;&amp;#x3C; &quot;Normal: &quot; &amp;#x3C;&amp;#x3C; n.x &amp;#x3C;&amp;#x3C; &quot; &quot; &amp;#x3C;&amp;#x3C; n.y &amp;#x3C;&amp;#x3C; &quot; &quot; &amp;#x3C;&amp;#x3C; n.z &amp;#x3C;&amp;#x3C; &quot;\\n&quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;return 0;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;code&gt;,     },     {       language: &quot;python&quot;,       label: &quot;Python: 핵심 연산&quot;,       code: &lt;/code&gt;def sub(a, b): return (a[0]-b[0], a[1]-b[1], a[2]-b[2])
def cross(a, b):
return (a[1]*b[2]-a[2]*b[1], a[2]*b[0]-a[0]*b[2], a[0]*b[1]-a[1]*b[0])
def dot(a, b): return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]&lt;/p&gt;
&lt;p&gt;def visible(A, B, C, P):
&quot;&quot;&quot;삼각형 ABC 에서 점 P 가 보이는지 (outward normal 기준)&quot;&quot;&quot;
ab = sub(B, A)
ac = sub(C, A)
n = cross(ab, ac)
return dot(n, sub(P, A)) &gt; 0&lt;/p&gt;
&lt;p&gt;A = (0,0,0); B = (1,0,0); C = (0,1,0); Q = (0,0,1)
print(“Q visible from ABC:”, visible(A, B, C, Q))  # True&lt;/p&gt;
&lt;p&gt;ab = sub(B, A); ac = sub(C, A)
n = cross(ab, ac)
print(“Normal:”, n)  # (0, 0, 1)&lt;code&gt;,     },   ]}   cases={[     {       label: &quot;기본&quot;,       input: &lt;/code&gt;3
0 0 0
1 0 0
0 1 0&lt;code&gt;,       output: &lt;/code&gt;Q visible from ABC: 1
Normal: 0 0 1`,
},
]}
/&gt;&lt;/p&gt;
&lt;h2 id=&quot;복잡도&quot;&gt;복잡도&lt;/h2&gt;









































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;알고리즘&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;시간&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;공간&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;비고&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Incremental&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;$O(N^2)$ worst&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;$O(N)$&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;구현 쉬움&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Incremental + 셔플&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;$O(N \log N)$ expected&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;$O(N)$&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;실전 추천&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Gift Wrapping&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;$O(N \cdot F)$&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;$O(F)$&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;F: 면 수&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Chan’s Algorithm&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;$O(N \log H)$&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;$O(N)$&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;이론적 최적&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Divide &amp;#x26; Conquer&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;$O(N \log N)$&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;$O(N)$&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;구현 복잡&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;볼록 껍질 면 수 F = O(N) (Euler’s formula 에 의해 V - E + F = 2, V+E+F = O(N)).&lt;/p&gt;
&lt;h2 id=&quot;함정&quot;&gt;함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
&lt;strong&gt;일반 위치 가정&lt;/strong&gt;: 4점이 같은 평면 (coplanar) 이면 초기 사면체 형성 불가. 랜덤 섭동 (perturbation) 으로 해결.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
&lt;strong&gt;수치 오차&lt;/strong&gt;: 부동소수점 사용 시 &lt;code&gt;visible()&lt;/code&gt; 판단이 틀릴 수 있음. 정수 좌표라면 long long 외적으로 정확하게 처리.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
&lt;strong&gt;법선 방향&lt;/strong&gt;: face 의 outward/inward normal 일관성 유지 필수. 방향 혼동 시 내부/외부 판단이 뒤집힘.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;흔한-실수&quot;&gt;흔한 실수&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;초기 사면체의 4점이 한 평면에 있는 경우 처리 안 함&lt;/li&gt;
&lt;li&gt;horizon edges 추출 시 방향 (winding order) 을 잘못 잡음&lt;/li&gt;
&lt;li&gt;무작위 셔플 없이 incremental 하면 O(N^2) worst case 에 걸림&lt;/li&gt;
&lt;li&gt;2D CH 와 달리 3D 에서는 단순 스택 기반 알고리즘 없음&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;boj-연습-문제&quot;&gt;BOJ 연습 문제&lt;/h2&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;번호&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;제목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;키워드&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1168&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;요세푸스 문제 2&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;선형 자료구조 응용&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 9240&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;로버트 후드&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;2D CH, 3D CH 기초&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 4181&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Convex Hull&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;2D CH 구현&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
BOJ 에서 3D CH 자체를 요구하는 문제는 드물고, 3D CH 의 핵심 서브루틴 (외적, 가시성 판단) 이 더 자주 등장.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[convex-hull|2D Convex Hull]]&lt;/li&gt;
&lt;li&gt;[[geometry-3d|3D Geometry]]&lt;/li&gt;
&lt;li&gt;[[geometry|기하 알고리즘 기초]]&lt;/li&gt;
&lt;li&gt;[[angle-sorting|Angle Sorting]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>algorithm</category><category>geometry</category><category>convex-hull</category><category>3d</category><author>koa (김신건)</author></item><item><title>Cycle Detection: 그래프 사이클 탐지</title><link>https://shinkeonkim.com/wiki/algorithm/graph/cycle-detection/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/algorithm/graph/cycle-detection/</guid><pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;그래프에 &lt;strong&gt;사이클이 존재하는가&lt;/strong&gt;, 또는 &lt;strong&gt;어느 사이클인가&lt;/strong&gt; 를 찾는 문제.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;무향 그래프&lt;/strong&gt;: 한 정점에서 출발해 다시 그 정점으로 돌아오는 경로.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;유향 그래프&lt;/strong&gt;: 방향을 따라가며 이미 방문 중인 정점으로 돌아오는 경로.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;문제-상황&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p&gt;위상 정렬, 의존성 검사, DAG 판별 등에서 사이클 존재 여부가 핵심이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;패키지 의존성&lt;/strong&gt;: A -&gt; B -&gt; C -&gt; A 이면 설치 불가 (순환 의존).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;작업 스케줄링&lt;/strong&gt;: 작업 간 선후관계에 사이클이 있으면 스케줄 불가능.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;코스 선수과목&lt;/strong&gt;: 수강 순서에 사이클이 있으면 모순.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;시각화&quot;&gt;시각화&lt;/h2&gt;
&lt;h3 id=&quot;무향-그래프-사이클&quot;&gt;무향 그래프 사이클&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A[&quot;1&quot;] --- B[&quot;2&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B --- C[&quot;3&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C --- D[&quot;4&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    D --- B&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2 - 3 - 4 - 2 가 사이클. Union-Find 에서 4-2 간선 삽입 시 &lt;code&gt;find(4) == find(2)&lt;/code&gt; 로 탐지.&lt;/p&gt;
&lt;h3 id=&quot;유향-그래프-사이클-dfs-3색&quot;&gt;유향 그래프 사이클 (DFS 3색)&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A2[&quot;1 (흰)&quot;] --&gt;|&quot;방문&quot;| B2[&quot;2 (회색)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B2 --&gt;|&quot;방문&quot;| C2[&quot;3 (회색)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C2 --&gt;|&quot;back edge!&quot;| B2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3 -&gt; 2 back edge: 회색(처리 중) 정점으로 가는 간선 = 사이클.&lt;/p&gt;
&lt;h2 id=&quot;핵심-아이디어&quot;&gt;핵심 아이디어&lt;/h2&gt;
&lt;h3 id=&quot;무향-그래프-union-find&quot;&gt;무향 그래프: Union-Find&lt;/h3&gt;
&lt;p&gt;간선 (u, v) 를 삽입할 때 &lt;code&gt;find(u) == find(v)&lt;/code&gt; 이면 u, v 가 이미 같은 컴포넌트 = 사이클.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for each edge (u, v):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if find(u) == find(v): CYCLE!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    else: union(u, v)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;유향-그래프-dfs-3색&quot;&gt;유향 그래프: DFS 3색&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;흰색 (0)&lt;/strong&gt;: 미방문&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;회색 (1)&lt;/strong&gt;: 현재 DFS 스택에 있음 (처리 중)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;검은색 (2)&lt;/strong&gt;: 처리 완료&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;회색 정점으로 향하는 간선 = &lt;strong&gt;back edge&lt;/strong&gt; = 사이클 존재.&lt;/p&gt;
&lt;h3 id=&quot;유향-그래프-kahns-알고리즘&quot;&gt;유향 그래프: Kahn’s 알고리즘&lt;/h3&gt;
&lt;p&gt;위상 정렬로 처리된 정점 수 &amp;#x3C; N 이면 사이클 존재 (일부 정점의 indegree 가 0이 되지 않음).&lt;/p&gt;
&lt;h2 id=&quot;알고리즘&quot;&gt;알고리즘&lt;/h2&gt;
&lt;h3 id=&quot;무향-그래프-union-find-1&quot;&gt;무향 그래프: Union-Find&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; DSU&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    vector&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;int&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; par, rank_;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    DSU&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; n&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) : &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;par&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(n), &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;rank_&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(n, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) { &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;iota&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(par.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(), par.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(), &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;); }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    int&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; find&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; x&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (par[x] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; x) par[x] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; find&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(par[x]);&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // path compression&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; par[x];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    bool&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; same&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; a&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; b&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) { &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; find&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(a) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; find&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(b); }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    void&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; merge&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; a&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; b&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        a &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; find&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(a); b &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; find&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(b);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (a &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; b) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (rank_[a] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; rank_[b]) &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;swap&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(a, b);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        par[b] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; a;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (rank_[a] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; rank_[b]) rank_[a]&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;bool&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; hasCycle_undirected&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; n&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;vector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;pair&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; edges&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    DSU &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;dsu&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(n);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;auto&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [u, v] : edges) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (dsu.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;same&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(u, v)) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        dsu.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;merge&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(u, v);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;유향-그래프-dfs-3색-1&quot;&gt;유향 그래프: DFS 3색&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;vector&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;int&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; color;&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // 0=흰, 1=회, 2=검&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;bool&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; dfs&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; u&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;vector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;vector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; adj&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    color[u] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // 회색: 처리 시작&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; v : adj[u]) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (color[v] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;              // back edge: 사이클&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (color[v] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; dfs&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(v, adj)) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    color[u] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // 검은색: 처리 완료&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;bool&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; hasCycle_directed&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; n&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;vector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;vector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; adj&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    color.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;assign&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(n, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n; i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (color[i] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; dfs&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(i, adj)) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;유향-그래프-kahns-알고리즘-1&quot;&gt;유향 그래프: Kahn’s 알고리즘&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;bool&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; hasCycle_kahn&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; n&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;vector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;vector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; adj&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    vector&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;int&gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; indeg&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(n, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; u &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; u &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n; u&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; v : adj[u]) indeg[v]&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    queue&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;int&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; q;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n; i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (indeg[i] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) q.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(i);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cnt &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    while&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;q.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;empty&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; u &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; q.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;front&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(); q.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;pop&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(); cnt&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; v : adj[u])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;--&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;indeg[v] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) q.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(v);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cnt &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n;&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // 처리 못한 정점 있으면 사이클&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;구현&quot;&gt;구현&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
variants={[
{
language: “cpp”,
label: “C++ (유향 그래프 DFS 3색)”,
code: `#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;&lt;/p&gt;
&lt;p&gt;int n, m;
vector&amp;#x3C;vector&lt;int&gt;&gt; adj;
vector&lt;int&gt; color;  // 0=흰, 1=회, 2=검&lt;/int&gt;&lt;/int&gt;&lt;/p&gt;
&lt;p&gt;bool dfs(int u) {
color[u] = 1;
for (int v : adj[u]) {
if (color[v] == 1) return true;
if (color[v] == 0 &amp;#x26;&amp;#x26; dfs(v)) return true;
}
color[u] = 2;
return false;
}&lt;/p&gt;
&lt;p&gt;int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cin &gt;&gt; n &gt;&gt; m;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;adj.resize(n + 1);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for (int i = 0; i &amp;#x3C; m; i++) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    int u, v; cin &gt;&gt; u &gt;&gt; v;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    adj[u].push_back(v);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;color.assign(n + 1, 0);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;bool cycle = false;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for (int i = 1; i &amp;#x3C;= n; i++)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if (color[i] == 0 &amp;#x26;&amp;#x26; dfs(i)) { cycle = true; break; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cout &amp;#x3C;&amp;#x3C; (cycle ? &quot;YES&quot; : &quot;NO&quot;) &amp;#x3C;&amp;#x3C; &quot;\\n&quot;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;code&gt;,     },     {       language: &quot;python&quot;,       label: &quot;Python (Kahn&apos;s 알고리즘)&quot;,       code: &lt;/code&gt;import sys
from collections import deque
input = sys.stdin.readline&lt;/p&gt;
&lt;p&gt;def solve():
n, m = map(int, input().split())
adj = [[] for _ in range(n + 1)]
indeg = [0] * (n + 1)
for _ in range(m):
u, v = map(int, input().split())
adj[u].append(v)
indeg[v] += 1&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;q = deque()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for i in range(1, n + 1):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if indeg[i] == 0:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        q.append(i)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cnt = 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;while q:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    u = q.popleft()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    cnt += 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for v in adj[u]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        indeg[v] -= 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        if indeg[v] == 0:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            q.append(v)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;print(&quot;YES&quot; if cnt &amp;#x3C; n else &quot;NO&quot;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;solve()&lt;code&gt;,     },   ]}   cases={[     {       label: &quot;사이클 있음&quot;,       input: &lt;/code&gt;4 4
1 2
2 3
3 4
4 2&lt;code&gt;,       output: &lt;/code&gt;YES&lt;code&gt;,     },     {       label: &quot;사이클 없음 (DAG)&quot;,       input: &lt;/code&gt;3 2
1 2
2 3&lt;code&gt;,       output: &lt;/code&gt;NO`,
},
]}
/&gt;&lt;/p&gt;
&lt;h2 id=&quot;복잡도&quot;&gt;복잡도&lt;/h2&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;방법&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;시간&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;공간&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;적용 대상&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Union-Find&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(E * alpha(N))&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;무향 그래프&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;DFS 3색&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(V + E)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(V)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;유향 그래프&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Kahn’s 알고리즘&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(V + E)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(V)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;유향 그래프&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Union-Find 는 alpha(N) (역 Ackermann): 사실상 O(1). DFS 3색과 Kahn’s 는 동일 복잡도지만 용도가 다름:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DFS 3색: 사이클 경로 자체를 복원할 때 유리.&lt;/li&gt;
&lt;li&gt;Kahn’s: 위상 정렬을 동시에 얻을 때 유리.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;함정&quot;&gt;함정&lt;/h2&gt;
&lt;h3 id=&quot;1-무향-그래프에서-부모-방문을-사이클로-혼동&quot;&gt;1. 무향 그래프에서 부모 방문을 사이클로 혼동&lt;/h3&gt;
&lt;p&gt;DFS 기반 무향 그래프 사이클 탐지 시, 간선 (u, v) 에서 v 를 방문할 때 &lt;strong&gt;부모 방향으로 돌아가는 것&lt;/strong&gt;을 back edge 로 착각하는 실수.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 잘못: 부모(par)로 돌아가는 것도 사이클 취급&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;bool&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; dfs&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; u&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; par&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, ...) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    visited[u] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; v : adj[u]) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;visited[v]) { &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;dfs&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(v, u, ...); }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        else&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // 부모 방문도 true -&gt; 오류!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 올바름: 부모 제외&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;bool&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; dfs&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; u&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; par&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, ...) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    visited[u] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; v : adj[u]) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (v &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; par) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;continue&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // 부모 방향 skip&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;visited[v]) { &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;dfs&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(v, u, ...)) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        else&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // 부모 아닌 방문 완료 정점 = back edge&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
멀티 그래프 (같은 정점 사이 간선 여러 개) 는 간선 인덱스로 부모를 추적해야 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;2-유향-그래프에서-흰색검은색-혼동&quot;&gt;2. 유향 그래프에서 흰색/검은색 혼동&lt;/h3&gt;
&lt;p&gt;검은색 (처리 완료) 정점으로 가는 간선은 &lt;strong&gt;forward edge&lt;/strong&gt; 또는 &lt;strong&gt;cross edge&lt;/strong&gt; 로 사이클이 아니다. 오직 &lt;strong&gt;회색 (처리 중)&lt;/strong&gt; 정점으로 가는 back edge 만 사이클.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
방문 여부만 체크 (&lt;code&gt;visited[]&lt;/code&gt;)하면 유향 그래프에서 오답. 반드시 3색 or 스택 상태 구분.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;3-kahns-로-경로-복원-불가&quot;&gt;3. Kahn’s 로 경로 복원 불가&lt;/h3&gt;
&lt;p&gt;Kahn’s 알고리즘은 사이클 &lt;strong&gt;존재 여부&lt;/strong&gt;만 알 수 있고, 어떤 정점이 사이클에 포함되는지 직접 알기 어렵다. 사이클 정점 목록이 필요하면 DFS 3색으로 복원.&lt;/p&gt;
&lt;h2 id=&quot;boj-연습-문제&quot;&gt;BOJ 연습 문제&lt;/h2&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;번호&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;제목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;비고&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 9466&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;텀 프로젝트&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;유향 그래프 사이클 (DFS)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1516&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;게임 개발&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;위상 정렬 + 사이클 없음 보장&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 2252&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;줄 세우기&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;위상 정렬 기본&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 10451&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;순열 사이클&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;무향 그래프 사이클 카운트&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[topological-sorting|위상 정렬 (Topological Sorting)]]&lt;/li&gt;
&lt;li&gt;[[disjoint-set|Union-Find (Disjoint Set)]]&lt;/li&gt;
&lt;li&gt;[[tarjan-scc|Tarjan SCC]] (강한 연결 요소)&lt;/li&gt;
&lt;li&gt;[[graph|그래프 기초]]&lt;/li&gt;
&lt;li&gt;[[dfs|DFS (깊이 우선 탐색)]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>algorithm</category><category>graph</category><category>cycle</category><category>dfs</category><category>union-find</category><author>koa (김신건)</author></item><item><title>DP on Bitmask: 비트마스크 DP</title><link>https://shinkeonkim.com/wiki/algorithm/dp/dp-on-bitmask/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/algorithm/dp/dp-on-bitmask/</guid><pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;부분집합 상태를 비트마스크로 인코딩&lt;/strong&gt;하여 DP를 수행하는 기법. 정수 &lt;code&gt;mask&lt;/code&gt;의 &lt;em&gt;i&lt;/em&gt;번째 비트가 1이면 “원소 i 선택”, 0이면 “미선택”을 의미한다. 원소 수 N ≤ 20 정도의 집합 문제에서 유효하다.&lt;/p&gt;
&lt;p&gt;$$
\text{dp}[mask][i] = \text{mask에 속한 원소들을 처리하고 현재 위치가 } i \text{ 일 때의 최적값}
$$&lt;/p&gt;
&lt;p&gt;비트 연산 기초: [[dp-bitfield|DP Bitfield]] 참조.&lt;/p&gt;
&lt;h2 id=&quot;문제-상황&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p&gt;“어떤 원소를 선택했는가” 자체가 상태인 최적화 문제:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TSP (Traveling Salesman Problem)&lt;/strong&gt;: 모든 도시를 한 번씩 방문하는 최소 비용 순환 경로&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Assignment Problem&lt;/strong&gt;: 사람과 태스크를 1:1로 매칭하여 비용 최소화&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hamiltonian Path&lt;/strong&gt;: 모든 정점을 방문하는 경로 존재 여부&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Set Cover&lt;/strong&gt;: 최소 개수의 집합으로 전체 원소를 커버&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;N ≤ 20이면 2^20 = 1,048,576으로 메모리와 시간 모두 허용 범위.&lt;/p&gt;
&lt;h2 id=&quot;시각화&quot;&gt;시각화&lt;/h2&gt;
&lt;p&gt;N=3 TSP 상태 전이 (도시 0 고정 출발):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S[&quot;출발: 도시 0만 방문&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A[&quot;도시 0, 1 방문, 현재 i=1&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B[&quot;도시 0, 2 방문, 현재 i=2&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C[&quot;도시 0, 1, 2 방문, 현재 i=2&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    D[&quot;도시 0, 1, 2 방문, 현재 i=1&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    E[&quot;종료 후보: i=2에서 복귀&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    F[&quot;종료 후보: i=1에서 복귀&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S --&gt;|&quot;도시 1 선택&quot;| A&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S --&gt;|&quot;도시 2 선택&quot;| B&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A --&gt;|&quot;도시 2 선택&quot;| C&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B --&gt;|&quot;도시 1 선택&quot;| D&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C --&gt; E&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    D --&gt; F&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;N=3 비트마스크 표현:&lt;/p&gt;


















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;mask (이진)&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;십진&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;선택된 도시&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;000&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;0&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;공집합&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;001&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;{0}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;010&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;2&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;{1}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;011&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;3&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;{0, 1}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;100&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;4&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;{2}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;101&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;5&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;{0, 2}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;110&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;6&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;{1, 2}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;111&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;7&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;{0, 1, 2}&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;핵심-아이디어&quot;&gt;핵심 아이디어&lt;/h2&gt;
&lt;p&gt;상태를 &lt;code&gt;(mask, i)&lt;/code&gt; 쌍으로 표현:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mask&lt;/code&gt;: 현재까지 방문/선택한 원소들의 비트마스크&lt;/li&gt;
&lt;li&gt;&lt;code&gt;i&lt;/code&gt;: 현재 위치 (마지막으로 선택한 원소)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;전이: 미방문 원소 j를 다음에 선택 → &lt;code&gt;(mask | (1&amp;#x3C;&amp;#x3C;j), j)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;핵심 비트 연산:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;i번째 포함 여부: &lt;code&gt;mask &amp;#x26; (1 &amp;#x3C;&amp;#x3C; i)&lt;/code&gt; 가 0이 아니면 포함&lt;/li&gt;
&lt;li&gt;i번째 원소 추가: &lt;code&gt;mask | (1 &amp;#x3C;&amp;#x3C; i)&lt;/code&gt; 로 새 mask 생성&lt;/li&gt;
&lt;li&gt;i번째 원소 제거: &lt;code&gt;mask &amp;#x26; ~(1 &amp;#x3C;&amp;#x3C; i)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;전체 집합: &lt;code&gt;(1 &amp;#x3C;&amp;#x3C; N) - 1&lt;/code&gt; (모든 비트 1)&lt;/li&gt;
&lt;li&gt;부분집합 순회: &lt;code&gt;for (int s = mask; s &gt; 0; s = (s-1) &amp;#x26; mask)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;tsp-점화식&quot;&gt;TSP 점화식&lt;/h3&gt;
&lt;p&gt;$$
\text{dp}[mask \mid (1 \ll j)][j] = \min\bigl(\text{dp}[mask \mid (1 \ll j)][j],; \text{dp}[mask][i] + \text{cost}[i][j]\bigr)
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;초기&lt;/strong&gt;: &lt;code&gt;dp[1][0] = 0&lt;/code&gt; (도시 0만 방문, 도시 0에 위치), 나머지 INF.
&lt;strong&gt;답&lt;/strong&gt;: &lt;code&gt;min(dp[(1&amp;#x3C;&amp;#x3C;N)-1][i] + cost[i][0])&lt;/code&gt; for i in 1..N-1.&lt;/p&gt;
&lt;h3 id=&quot;assignment-problem-변형&quot;&gt;Assignment Problem 변형&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;dp[i][mask]&lt;/code&gt; = i번째 사람까지 처리, 사용된 태스크 집합 = mask.&lt;/p&gt;
&lt;p&gt;$$
\text{dp}[i+1][mask \mid (1 \ll j)] = \min(\text{dp}[i+1][mask \mid (1 \ll j)],; \text{dp}[i][mask] + \text{cost}[i][j])
$$&lt;/p&gt;
&lt;p&gt;TSP와 달리 외부 루프가 사람 인덱스 i, 내부 루프가 mask. &lt;code&gt;popcount(mask) == i&lt;/code&gt; 조건으로 유효 상태만 처리.&lt;/p&gt;
&lt;h3 id=&quot;부분집합-열거-패턴&quot;&gt;부분집합 열거 패턴&lt;/h3&gt;
&lt;p&gt;모든 부분집합을 열거하는 표준 패턴:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// mask의 모든 부분집합 열거 (공집합 포함)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; s &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; mask; ; s &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (s &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; mask) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    process&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(s);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (s &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;break&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;모든 mask에 대해 실행하면 O(3^N) (각 원소가 mask 포함/부분집합 포함/부분집합 미포함 3가지).&lt;/p&gt;
&lt;h2 id=&quot;알고리즘&quot;&gt;알고리즘&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# TSP 비트마스크 DP&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;dp[1][0] = 0          # 도시 0 방문, 도시 0 위치&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for mask = 1 to (1&amp;#x3C;&amp;#x3C;N)-1:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for i = 0 to N-1:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        if dp[mask][i] == INF: continue&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        if not (mask &amp;#x26; (1&amp;#x3C;&amp;#x3C;i)): continue  # i가 mask에 없으면 무효 상태&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        for j = 0 to N-1:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            if mask &amp;#x26; (1&amp;#x3C;&amp;#x3C;j): continue    # 이미 방문한 도시&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            nmask = mask | (1 &amp;#x3C;&amp;#x3C; j)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            dp[nmask][j] = min(dp[nmask][j], dp[mask][i] + cost[i][j])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;full = (1 &amp;#x3C;&amp;#x3C; N) - 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;answer = min(dp[full][i] + cost[i][0] for i in 1..N-1)  # 원점 복귀&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;구현&quot;&gt;구현&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
variants={[
{
language: “cpp”,
label: “C++”,
code: `#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;
const int INF = 1e9;&lt;/p&gt;
&lt;p&gt;int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;int n;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cin &gt;&gt; n;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;vector&amp;#x3C;vector&amp;#x3C;int&gt;&gt; cost(n, vector&amp;#x3C;int&gt;(n));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for (int i = 0; i &amp;#x3C; n; i++)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for (int j = 0; j &amp;#x3C; n; j++)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        cin &gt;&gt; cost[i][j];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// dp[mask][i]: mask 집합 방문 후 i에 있을 때 최소 비용&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;vector&amp;#x3C;vector&amp;#x3C;int&gt;&gt; dp(1 &amp;#x3C;&amp;#x3C; n, vector&amp;#x3C;int&gt;(n, INF));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;dp[1][0] = 0;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for (int mask = 1; mask &amp;#x3C; (1 &amp;#x3C;&amp;#x3C; n); mask++) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for (int i = 0; i &amp;#x3C; n; i++) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        if (dp[mask][i] == INF) continue;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        if (!(mask &amp;#x26; (1 &amp;#x3C;&amp;#x3C; i))) continue;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        for (int j = 0; j &amp;#x3C; n; j++) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            if (mask &amp;#x26; (1 &amp;#x3C;&amp;#x3C; j)) continue;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            int nmask = mask | (1 &amp;#x3C;&amp;#x3C; j);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            dp[nmask][j] = min(dp[nmask][j], dp[mask][i] + cost[i][j]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;int full = (1 &amp;#x3C;&amp;#x3C; n) - 1;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;int ans = INF;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for (int i = 1; i &amp;#x3C; n; i++)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ans = min(ans, dp[full][i] + cost[i][0]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cout &amp;#x3C;&amp;#x3C; ans &amp;#x3C;&amp;#x3C; &quot;\\n&quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;return 0;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;code&gt;,     },     {       language: &quot;python&quot;,       label: &quot;Python&quot;,       code: &lt;/code&gt;import sys
input = sys.stdin.readline&lt;/p&gt;
&lt;p&gt;def solve():
n = int(input())
cost = [list(map(int, input().split())) for _ in range(n)]
INF = float(‘inf’)&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# dp[mask][i]: mask 집합 방문 후 i에 있을 때 최소 비용&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;dp = [[INF] * n for _ in range(1 &amp;#x3C;&amp;#x3C; n)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;dp[1][0] = 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for mask in range(1, 1 &amp;#x3C;&amp;#x3C; n):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for i in range(n):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        if dp[mask][i] == INF or not (mask &gt;&gt; i &amp;#x26; 1):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            continue&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        for j in range(n):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            if mask &gt;&gt; j &amp;#x26; 1:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                continue&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            nmask = mask | (1 &amp;#x3C;&amp;#x3C; j)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            cand = dp[mask][i] + cost[i][j]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            if cand &amp;#x3C; dp[nmask][j]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                dp[nmask][j] = cand&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;full = (1 &amp;#x3C;&amp;#x3C; n) - 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;print(min(dp[full][i] + cost[i][0] for i in range(1, n)))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;solve()&lt;code&gt;,     },     {       language: &quot;java&quot;,       label: &quot;Java&quot;,       code: &lt;/code&gt;import java.util.*;&lt;/p&gt;
&lt;p&gt;public class Main {
static final int INF = Integer.MAX_VALUE / 2;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;public static void main(String[] args) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    Scanner sc = new Scanner(System.in);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    int n = sc.nextInt();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    int[][] cost = new int[n][n];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for (int i = 0; i &amp;#x3C; n; i++)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        for (int j = 0; j &amp;#x3C; n; j++)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            cost[i][j] = sc.nextInt();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // dp[mask][i]: mask 집합 방문 후 i에 있을 때 최소 비용&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    int[][] dp = new int[1 &amp;#x3C;&amp;#x3C; n][n];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for (int[] row : dp) Arrays.fill(row, INF);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    dp[1][0] = 0;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for (int mask = 1; mask &amp;#x3C; (1 &amp;#x3C;&amp;#x3C; n); mask++) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        for (int i = 0; i &amp;#x3C; n; i++) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            if (dp[mask][i] == INF) continue;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            if ((mask &amp;#x26; (1 &amp;#x3C;&amp;#x3C; i)) == 0) continue;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            for (int j = 0; j &amp;#x3C; n; j++) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                if ((mask &amp;#x26; (1 &amp;#x3C;&amp;#x3C; j)) != 0) continue;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                int nmask = mask | (1 &amp;#x3C;&amp;#x3C; j);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                dp[nmask][j] = Math.min(dp[nmask][j], dp[mask][i] + cost[i][j]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    int full = (1 &amp;#x3C;&amp;#x3C; n) - 1;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    int ans = INF;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for (int i = 1; i &amp;#x3C; n; i++)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        ans = Math.min(ans, dp[full][i] + cost[i][0]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    System.out.println(ans);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;code&gt;,     },   ]}   cases={[     {       label: &quot;4개 도시 TSP&quot;,       input: &lt;/code&gt;4
0 10 15 20
10 0 35 25
15 35 0 30
20 25 30 0&lt;code&gt;,       output: &lt;/code&gt;80&lt;code&gt;,     },     {       label: &quot;3개 도시&quot;,       input: &lt;/code&gt;3
0 1 2
1 0 3
2 3 0&lt;code&gt;,       output: &lt;/code&gt;6`,
},
]}
/&gt;&lt;/p&gt;
&lt;h2 id=&quot;복잡도&quot;&gt;복잡도&lt;/h2&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;항목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;값&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;시간&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(2^N · N²)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;공간&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(2^N · N)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;N=15&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;2^15 · 225 ≈ 7.4×10^6 (여유)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;N=20&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;2^20 · 400 ≈ 4.2×10^8 (타이트)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;실용 한계&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;N ≤ 20 (int 기준 메모리 약 80MB)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
N=25 이상은 2^25 ≈ 33M 상태로 메모리와 시간 모두 한계 초과. N 제약 반드시 확인.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;함정&quot;&gt;함정&lt;/h2&gt;
&lt;h3 id=&quot;1-배열-크기-스택-오버플로&quot;&gt;1. 배열 크기: 스택 오버플로&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 지역 변수로 선언하면 스택 오버플로&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dp[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x3C;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 20&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;][&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;];&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // 전역 선언 필수 (약 80MB)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2-출발-도시-고정-누락&quot;&gt;2. 출발 도시 고정 누락&lt;/h3&gt;
&lt;p&gt;TSP는 순환 경로이므로 도시 0 고정 출발이 최적. 모든 도시에서 시작하면 정답이 N배 중복으로 잘못 계산될 수 있다.&lt;/p&gt;
&lt;h3 id=&quot;3-mask에-i-포함-여부-체크-누락&quot;&gt;3. mask에 i 포함 여부 체크 누락&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(mask &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x3C;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i))) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;continue&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 없으면 i가 mask에 없는 잘못된 상태에서 전이 발생&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;4-원점-복귀-비용-누락&quot;&gt;4. 원점 복귀 비용 누락&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;dp[(1&amp;#x3C;&amp;#x3C;N)-1][i]&lt;/code&gt;는 i에 있는 비용. 최종 답은 &lt;code&gt;dp[full][i] + cost[i][0]&lt;/code&gt;로 원점 복귀 포함.&lt;/p&gt;
&lt;h3 id=&quot;5-assignment-problem과-tsp-혼동&quot;&gt;5. Assignment Problem과 TSP 혼동&lt;/h3&gt;
&lt;p&gt;Assignment Problem에서는 &lt;code&gt;dp[i][mask]&lt;/code&gt; = i번째 사람까지 처리, mask = 사용된 태스크. 전이 방향이 TSP와 다르므로 혼동 주의.&lt;/p&gt;
&lt;h2 id=&quot;boj-연습-문제&quot;&gt;BOJ 연습 문제&lt;/h2&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;번호&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;제목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;유형&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 2098&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;외판원 순회&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;TSP (핵심 문제)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1194&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;달이 차오른다, 가자.&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;BFS + bitmask&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 17471&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;게리맨더링&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;지역 분할&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1562&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;계단 수&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Digit DP + bitmask&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 2234&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;성곽&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;지역 합병 bitmask&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[dp-bitfield|DP Bitfield]] (비트 연산 상세)&lt;/li&gt;
&lt;li&gt;[[tsp|TSP]]&lt;/li&gt;
&lt;li&gt;[[hamiltonian-path|Hamiltonian Path]]&lt;/li&gt;
&lt;li&gt;[[dp-sum-over-subsets|SOS DP]]&lt;/li&gt;
&lt;li&gt;[[dp-optimization|DP Optimization]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>algorithm</category><category>dp</category><category>bitmask</category><category>subset</category><author>koa (김신건)</author></item><item><title>DP Optimization: CHT, D&amp;C, Knuth, SMAWK</title><link>https://shinkeonkim.com/wiki/algorithm/dp/dp-optimization/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/algorithm/dp/dp-optimization/</guid><pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;일반 DP 를 O(N²) 또는 O(N³) 에서 &lt;strong&gt;O(N log N)&lt;/strong&gt; 또는 &lt;strong&gt;O(N)&lt;/strong&gt; 으로 낮추는 여러 최적화 기법의 총칭입니다. 모두 특정 &lt;strong&gt;조건 (monotone, concave, quadrangle inequality)&lt;/strong&gt; 이 성립할 때만 적용 가능합니다.&lt;/p&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;기법&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;원래 복잡도&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;최적화 후&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;적용 조건&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Convex Hull Trick&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N²)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N log N) / O(N)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;선형 함수 min/max, 기울기 단조&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Divide and Conquer&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N²)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N log N)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;최적 k 단조 증가&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Knuth Optimization&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N³)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N²)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Quadrangle Inequality + 단조&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;SMAWK / Monge&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N²)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Monge 행렬&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;문제-상황&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p&gt;전형적인 DP 전환 문제를 만났을 때, 아래 형태인지 먼저 확인합니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 형태 A: CHT / SMAWK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;dp[i] = min over j&amp;#x3C;i of { f(j) * x[i] + g(j) }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// f(j) = 기울기, x[i] = 현재 값, g(j) = 절편&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 형태 B: D&amp;#x26;C Optimization&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;dp[i][j] = min over k &amp;#x3C; j of { dp[i-1][k] + cost(k, j) }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 최적 k 가 j 에 대해 단조 증가&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 형태 C: Knuth&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;dp[i][j] = min over i &amp;#x3C;= k &amp;#x3C;= j of { dp[i][k] + dp[k][j] + w(i, j) }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// w 가 QI + 단조 만족&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;시각화&quot;&gt;시각화&lt;/h2&gt;
&lt;p&gt;4 가지 기법의 적용 시나리오와 복잡도.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Start[&quot;DP 전환 문제&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1{&quot;dp[i] = min f(j)*x[i]+g(j)?&quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q2{&quot;최적 k 단조 증가?&quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q3{&quot;구간 DP, w가 QI?&quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q4{&quot;Monge 행렬?&quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    CHT[&quot;CHT\nO(N log N) / O(N)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    DnC[&quot;D&amp;#x26;C Optimization\nO(N log N)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Knuth[&quot;Knuth Optimization\nO(N^2)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    SMAWK[&quot;SMAWK\nO(N)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Naive[&quot;Naive O(N^2) / O(N^3)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Start --&gt; Q1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 --&gt;|&quot;Yes&quot;| CHT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q1 --&gt;|&quot;No&quot;| Q2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q2 --&gt;|&quot;Yes&quot;| DnC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q2 --&gt;|&quot;No&quot;| Q3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q3 --&gt;|&quot;Yes&quot;| Knuth&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q3 --&gt;|&quot;No&quot;| Q4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q4 --&gt;|&quot;Yes&quot;| SMAWK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q4 --&gt;|&quot;No&quot;| Naive&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;핵심-아이디어&quot;&gt;핵심 아이디어&lt;/h2&gt;
&lt;h3 id=&quot;1-convex-hull-trick-cht&quot;&gt;1. Convex Hull Trick (CHT)&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;dp[i] = min_{j&amp;#x3C;i}(a[j] * x[i] + b[j])&lt;/code&gt; 형태. 선형 함수 집합의 최솟값 = &lt;strong&gt;하부 볼록 껍질 (lower envelope)&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Li-Chao Tree&lt;/strong&gt;: 임의 삽입 순서, O(log N) per query/update&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;단조 CHT (Monotone Stack)&lt;/strong&gt;: 기울기 &lt;code&gt;a[j]&lt;/code&gt; 단조 증가이면 O(1) push, O(1)/O(log N) query&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kinetic Heaps&lt;/strong&gt;: 온라인, 자동 최솟값 추적&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;전용 [[li-chao-tree|Li-Chao Tree]] 참조.&lt;/p&gt;
&lt;h3 id=&quot;2-divide-and-conquer-optimization&quot;&gt;2. Divide and Conquer Optimization&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;dp[i][j] = min_{k}(dp[i-1][k] + cost(k, j))&lt;/code&gt; 에서 &lt;strong&gt;최적 k 가 j 에 대해 단조 증가&lt;/strong&gt; 하면 O(N²) → O(N log N).&lt;/p&gt;
&lt;p&gt;핵심: 구간 [lo, hi] 의 중간 mid 를 계산하고, 가능한 k 범위를 재귀적으로 좁힘.&lt;/p&gt;
&lt;h3 id=&quot;3-knuth-optimization&quot;&gt;3. Knuth Optimization&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;dp[i][j] = min_{i&amp;#x3C;=k&amp;#x3C;=j}(dp[i][k] + dp[k][j]) + w(i,j)&lt;/code&gt; 에서 &lt;strong&gt;Quadrangle Inequality (QI)&lt;/strong&gt; 와 &lt;strong&gt;단조성&lt;/strong&gt; 이 성립하면 O(N³) → O(N²).&lt;/p&gt;
&lt;p&gt;QI: &lt;code&gt;w(a,c) + w(b,d) &amp;#x3C;= w(a,d) + w(b,c)&lt;/code&gt; (a&amp;#x3C;=b&amp;#x3C;=c&amp;#x3C;=d).&lt;/p&gt;
&lt;h3 id=&quot;4-smawk--monge&quot;&gt;4. SMAWK / Monge&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Monge 행렬&lt;/strong&gt;: 모든 2×2 부분행렬이 QI 를 만족하는 행렬. SMAWK 알고리즘으로 각 행의 최솟값을 O(N) 에 계산.&lt;/p&gt;
&lt;h2 id=&quot;알고리즘&quot;&gt;알고리즘&lt;/h2&gt;
&lt;h3 id=&quot;cht-단조-스택-오프라인&quot;&gt;CHT (단조 스택, 오프라인)&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;라인 관리 스택 stk:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  add(a, b):  // y = a*x + b 삽입&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      새 직선 L&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      while stk.size() &gt;= 2:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          if stk[-2] 와 stk[-1] 의 교점 &gt;= stk[-1] 와 L 의 교점:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              stk.pop()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          else: break&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      stk.push(L)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  query(x):   // 최솟값 직선의 y값&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      while stk.size() &gt;= 2:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          if stk[0].eval(x) &gt;= stk[1].eval(x):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              stk.pop_front()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          else: break&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      return stk[0].eval(x)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;dc-optimization&quot;&gt;D&amp;#x26;C Optimization&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;solve(lo, hi, optL, optR):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if lo &gt; hi: return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    mid = (lo + hi) / 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    best_k = optL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    dp[i][mid] = INF&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for k in [optL .. min(mid, optR)]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        cand = dp[i-1][k] + cost(k, mid)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        if cand &amp;#x3C; dp[i][mid]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            dp[i][mid] = cand&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            best_k = k&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    solve(lo, mid-1, optL, best_k)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    solve(mid+1, hi, best_k, optR)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;knuth-optimization&quot;&gt;Knuth Optimization&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// opt[i][j] = dp[i][j] 의 최적 k&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for gap in 1..n:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for i in 0..n-gap:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        j = i + gap&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        dp[i][j] = INF&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        for k in opt[i][j-1]..opt[i+1][j]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            if dp[i][k] + dp[k][j] + w(i,j) &amp;#x3C; dp[i][j]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                dp[i][j] = dp[i][k] + dp[k][j] + w(i,j)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                opt[i][j] = k&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;구현&quot;&gt;구현&lt;/h2&gt;
&lt;h3 id=&quot;dc-optimization-1&quot;&gt;D&amp;#x26;C Optimization&lt;/h3&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
language=“cpp”
label=“C++ (D&amp;#x26;C Optimization)”
outputLanguage=“text”
outputLabel=“결과”
title=“1D1D DP D&amp;#x26;C 최적화 (스캐폴딩)”
code={`#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;&lt;/p&gt;
&lt;p&gt;const long long INF = 1e18;
int n, m;
vector&lt;long long=&quot;&quot;&gt; prev_dp, cur_dp;&lt;/long&gt;&lt;/p&gt;
&lt;p&gt;// cost(k, j): 구체적인 문제마다 다름
long long cost(int k, int j) {
// 예: prefix sum 기반 비용
return (long long)(j - k) * (j - k);
}&lt;/p&gt;
&lt;p&gt;void solve(int lo, int hi, int optL, int optR) {
if (lo &gt; hi) return;
int mid = (lo + hi) / 2;
int best_k = optL;
cur_dp[mid] = INF;
for (int k = optL; k &amp;#x3C;= min(mid - 1, optR); k++) {
if (prev_dp[k] == INF) continue;
long long cand = prev_dp[k] + cost(k, mid);
if (cand &amp;#x3C; cur_dp[mid]) {
cur_dp[mid] = cand;
best_k = k;
}
}
solve(lo, mid - 1, optL, best_k);
solve(mid + 1, hi, best_k, optR);
}&lt;/p&gt;
&lt;p&gt;int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin &gt;&gt; n &gt;&gt; m;
prev_dp.resize(n + 1, INF);
cur_dp.resize(n + 1, INF);
prev_dp[0] = 0;
// i번째 분할
for (int i = 1; i &amp;#x3C;= m; i++) {
fill(cur_dp.begin(), cur_dp.end(), INF);
solve(1, n, 0, n - 1);
swap(prev_dp, cur_dp);
}
cout &amp;#x3C;&amp;#x3C; prev_dp[n] &amp;#x3C;&amp;#x3C; “\n”;
return 0;
}&lt;code&gt;}   output={&lt;/code&gt;// n=5, m=2 (2그룹으로 분할)
// 예시 cost = (j-k)^2
// dp[5] = 최소 분할 비용
8`}
/&gt;&lt;/p&gt;
&lt;h3 id=&quot;knuth-optimization-1&quot;&gt;Knuth Optimization&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Knuth: 구간 DP O(N^2)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 전제: w(i,j) 가 QI + 단조 만족&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;vector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;vector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;long&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; long&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&gt; &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;dp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;n&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;vector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;long&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; long&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;(n&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, INF));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;vector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;vector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&gt; &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;opt&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;n&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;vector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;(n&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n; i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) { dp[i][i] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; opt[i][i] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gap &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; gap &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n; gap&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gap &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n; i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; j &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gap;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        dp[i][j] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; INF;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; k &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; opt[i][j&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]; k &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; opt[i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;][j]; k&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            long&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; long&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cand &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dp[i][k] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dp[k][j] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; w&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(i, j);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (cand &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dp[i][j]) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                dp[i][j] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cand;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                opt[i][j] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; k;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;복잡도&quot;&gt;복잡도&lt;/h2&gt;









































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;기법&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;시간&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;메모리&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;제약 조건&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;CHT (단조 스택)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;기울기/쿼리 모두 단조&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;CHT (Li-Chao Tree)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N log N)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;임의 순서&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;D&amp;#x26;C Optimization&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N log N)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;최적 k 단조&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Knuth Optimization&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N²)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N²)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;QI + 단조&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;SMAWK&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Monge 행렬&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;함정&quot;&gt;함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
&lt;strong&gt;CHT 에서 기울기 정렬 미확인&lt;/strong&gt;: 단조 스택 CHT 는 기울기가 단조일 때만 O(1) push. 임의 순서이면 &lt;code&gt;deque&lt;/code&gt; 대신 Li-Chao Tree 를 써야 함.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
&lt;strong&gt;D&amp;#x26;C Optimization 의 optL/optR 범위&lt;/strong&gt;: &lt;code&gt;solve(lo, mid-1, optL, best_k)&lt;/code&gt; 에서 best_k 가 optR 보다 커지지 않도록 주의. &lt;code&gt;min(mid-1, optR)&lt;/code&gt; 처리 필요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
&lt;strong&gt;Knuth 에서 QI 검증 생략&lt;/strong&gt;: 반례가 있어도 AC 가 나오는 경우가 있어 QI 를 검증하지 않고 제출하는 실수. 반드시 &lt;code&gt;w(a,c) + w(b,d) &amp;#x3C;= w(a,d) + w(b,c)&lt;/code&gt; 를 수식으로 확인.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;적용 가능한 기법이 여러 가지라면 CHT &gt; D&amp;#x26;C &gt; Knuth 순으로 빠름.&lt;/li&gt;
&lt;li&gt;SMAWK 는 구현이 까다로워 실전에서는 LARSSON 또는 Divide Conquer 로 대체하는 경우가 많음.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;boj&quot;&gt;BOJ&lt;/h2&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;문제&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;분류&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/4008&quot;&gt;BOJ 4008 특공대&lt;/a&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;CHT 기본&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/13261&quot;&gt;BOJ 13261 탈옥&lt;/a&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;D&amp;#x26;C Optimization&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/11001&quot;&gt;BOJ 11001 김치&lt;/a&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;CHT (단조)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/10067&quot;&gt;BOJ 10067 수열 나누기&lt;/a&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;D&amp;#x26;C Optimization&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1648&quot;&gt;BOJ 1648 격자 행렬의 비용&lt;/a&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Knuth Optimization&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[li-chao-tree|Li-Chao Tree]] (CHT 자료구조)&lt;/li&gt;
&lt;li&gt;[[dp|DP 기초]]&lt;/li&gt;
&lt;li&gt;[[dp-bitfield|DP Bitfield]] (비트마스크 DP)&lt;/li&gt;
&lt;li&gt;[[knapsack|Knapsack DP]] (배낭 DP)&lt;/li&gt;
&lt;li&gt;[[knuth|Knuth Optimization]] (전용 문서)&lt;/li&gt;
&lt;li&gt;[[divide-and-conquer-optimization|D&amp;#x26;C Optimization]] (전용 문서)&lt;/li&gt;
&lt;li&gt;[[cht|Convex Hull Trick]] (전용 문서)&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>algorithm</category><category>dp</category><category>optimization</category><author>koa (김신건)</author></item><item><title>Dynamic Segment Tree: 지연 노드 생성</title><link>https://shinkeonkim.com/wiki/algorithm/data-structure/dynamic-segtree/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/algorithm/data-structure/dynamic-segtree/</guid><pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Dynamic Segment Tree&lt;/strong&gt; 는 값 범위가 매우 크지만 실제 접근 위치가 희소할 때, &lt;strong&gt;필요한 노드만 동적으로 생성&lt;/strong&gt; 하는 세그먼트 트리.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;공간&lt;/strong&gt;: O(Q log V), Q = 쿼리 수, V = 값 범위 (최대 10^18).&lt;/p&gt;
&lt;p&gt;일반 세그먼트 트리는 O(V) 공간 필요 → V = 10^9 이면 4GB, 불가능. 동적 세그먼트 트리는 실제 접근한 위치만 노드 생성.&lt;/p&gt;
&lt;h2 id=&quot;문제-상황과-동기&quot;&gt;문제 상황과 동기&lt;/h2&gt;
&lt;h3 id=&quot;언제-필요한가&quot;&gt;언제 필요한가&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;좌표 범위가 극단적&lt;/strong&gt;: 1 &amp;#x3C;= x &amp;#x3C;= 10^9, 하지만 쿼리 수 Q &amp;#x3C;= 10^5&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;온라인 쿼리&lt;/strong&gt;: 좌표 압축을 위해 쿼리를 미리 알아야 하지만 불가능한 경우&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;버전 추적 불필요&lt;/strong&gt;: Persistent Segtree 보다 구현 단순&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;방법-비교&quot;&gt;방법 비교&lt;/h3&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;방법&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;전제 조건&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;공간&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;복잡도&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;일반 세그트리&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;V 작음&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(V)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;낮음&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;좌표 압축 + 세그&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;오프라인 처리&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(Q)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;낮음&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;동적 세그트리&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;온라인, V 큼&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;O(Q log V)&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;중간&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;[[persistent-segtree&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Persistent Segtree]]&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;버전 추적 필요&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(Q log V)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;시각화&quot;&gt;시각화&lt;/h2&gt;
&lt;h3 id=&quot;희소-노드-생성-원리&quot;&gt;희소 노드 생성 원리&lt;/h3&gt;
&lt;p&gt;범위 [1, 10^9] 에서 쿼리 3개: 위치 100, 500000000, 900000000.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Root[&quot;루트 전체 범위&quot;] --&gt; L[&quot;왼쪽 절반&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Root --&gt; R[&quot;오른쪽 절반&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    L --&gt; LL[&quot;왼쪽 1/4&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    L --&gt; LR[&quot;오른쪽 1/4&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    LL --&gt; Leaf1[&quot;위치 100 (생성)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    LR --&gt; Leaf2[&quot;위치 500000000 (생성)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    R --&gt; Leaf3[&quot;위치 900000000 (생성)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    style Leaf1 fill:#ccffcc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    style Leaf2 fill:#ccffcc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    style Leaf3 fill:#ccffcc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3개 쿼리 = 약 3 x log(10^9) = 약 90개 노드만 생성. 나머지는 nullptr.&lt;/p&gt;
&lt;h3 id=&quot;포인터-노드-구조&quot;&gt;포인터 노드 구조&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Node[&quot;Node: sum, left, right&quot;] --&gt; Null[&quot;left = nullptr (미접근)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Node --&gt; Child[&quot;right = 자식 (접근 시 생성)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Child --&gt; GC[&quot;손자 노드들...&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;핵심-아이디어&quot;&gt;핵심 아이디어&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Lazy node creation&lt;/strong&gt;: 자식에 실제 접근할 때만 &lt;code&gt;new Node()&lt;/code&gt; 생성. nullptr 이면 값은 기본값 0.&lt;/p&gt;
&lt;p&gt;두 가지 구현 방식:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;포인터 기반&lt;/strong&gt;: &lt;code&gt;Node* left, *right&lt;/code&gt; - 직관적, 동적 메모리 할당&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pool allocator&lt;/strong&gt;: 배열 풀 미리 확보, 인덱스로 자식 참조 - 캐시 친화, new 오버헤드 없음&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;실전에서는 &lt;strong&gt;Pool allocator&lt;/strong&gt; 권장 (TLE 방지).&lt;/p&gt;
&lt;h2 id=&quot;알고리즘&quot;&gt;알고리즘&lt;/h2&gt;
&lt;h3 id=&quot;update-점-업데이트&quot;&gt;Update (점 업데이트)&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;update(node, lo, hi, pos, delta):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if node is null: node = new Node()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if lo == hi:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        node.sum += delta&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    mid = (lo + hi) / 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if pos &amp;#x3C;= mid: update(node.left, lo, mid, pos, delta)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    else: update(node.right, mid+1, hi, pos, delta)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    node.sum = (node.left?.sum or 0) + (node.right?.sum or 0)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;query-구간-합&quot;&gt;Query (구간 합)&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;query(node, lo, hi, ql, qr):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if node is null: return 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if ql &amp;#x3C;= lo and hi &amp;#x3C;= qr: return node.sum&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    mid = (lo + hi) / 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    result = 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if ql &amp;#x3C;= mid: result += query(node.left, lo, mid, ql, qr)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if qr &gt; mid: result += query(node.right, mid+1, hi, ql, qr)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return result&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;구현&quot;&gt;구현&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
variants={[
{
language: “cpp”,
label: “C++”,
code: `// Dynamic Segment Tree - 구간 합
// 범위 [1, 10^9], 포인터 기반 + Pool allocator 두 방법
#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;&lt;/p&gt;
&lt;p&gt;// ===== 방법 1: 포인터 기반 =====
struct NodeP {
long long sum = 0;
NodeP *l = nullptr, *r = nullptr;
};&lt;/p&gt;
&lt;p&gt;void update_p(NodeP*&amp;#x26; n, long long lo, long long hi, long long pos, long long v) {
if (!n) n = new NodeP();
if (lo == hi) { n-&gt;sum += v; return; }
long long mid = (lo + hi) / 2;
if (pos &amp;#x3C;= mid) update_p(n-&gt;l, lo, mid, pos, v);
else            update_p(n-&gt;r, mid+1, hi, pos, v);
n-&gt;sum = (n-&gt;l ? n-&gt;l-&gt;sum : 0) + (n-&gt;r ? n-&gt;r-&gt;sum : 0);
}&lt;/p&gt;
&lt;p&gt;long long query_p(NodeP* n, long long lo, long long hi, long long ql, long long qr) {
if (!n) return 0;
if (ql &amp;#x3C;= lo &amp;#x26;&amp;#x26; hi &amp;#x3C;= qr) return n-&gt;sum;
long long mid = (lo + hi) / 2, res = 0;
if (ql &amp;#x3C;= mid) res += query_p(n-&gt;l, lo, mid, ql, qr);
if (qr &gt; mid)  res += query_p(n-&gt;r, mid+1, hi, ql, qr);
return res;
}&lt;/p&gt;
&lt;p&gt;// ===== 방법 2: Pool allocator (실전 권장) =====
const int POOL = 6000000;  // Q * log(V) 여유 있게
struct NodeA { long long sum; int l, r; } pool[POOL];
int pcnt = 1;  // 0 은 null 역할&lt;/p&gt;
&lt;p&gt;void update_a(int n, long long lo, long long hi, long long pos, long long v) {
pool[n].sum += v;
if (lo == hi) return;
long long mid = (lo + hi) / 2;
if (pos &amp;#x3C;= mid) {
if (!pool[n].l) pool[n].l = pcnt++;
update_a(pool[n].l, lo, mid, pos, v);
} else {
if (!pool[n].r) pool[n].r = pcnt++;
update_a(pool[n].r, mid+1, hi, pos, v);
}
}&lt;/p&gt;
&lt;p&gt;long long query_a(int n, long long lo, long long hi, long long ql, long long qr) {
if (!n) return 0;
if (ql &amp;#x3C;= lo &amp;#x26;&amp;#x26; hi &amp;#x3C;= qr) return pool[n].sum;
long long mid = (lo + hi) / 2, res = 0;
if (ql &amp;#x3C;= mid) res += query_a(pool[n].l, lo, mid, ql, qr);
if (qr &gt; mid)  res += query_a(pool[n].r, mid+1, hi, ql, qr);
return res;
}&lt;/p&gt;
&lt;p&gt;int main() {
const long long V = 1e9;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 포인터 기반&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;NodeP* root_p = nullptr;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;update_p(root_p, 1, V, 100LL, 5);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;update_p(root_p, 1, V, 500000000LL, 3);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;update_p(root_p, 1, V, 900000000LL, 7);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cout &amp;#x3C;&amp;#x3C; &quot;포인터: [100, 9e8] = &quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     &amp;#x3C;&amp;#x3C; query_p(root_p, 1, V, 100, 900000000LL) &amp;#x3C;&amp;#x3C; &quot;\\n&quot;;  // 15&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// Pool allocator&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;int root_a = pcnt++;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;update_a(root_a, 1, V, 100LL, 5);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;update_a(root_a, 1, V, 500000000LL, 3);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cout &amp;#x3C;&amp;#x3C; &quot;Pool: [1, 5e8] = &quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     &amp;#x3C;&amp;#x3C; query_a(root_a, 1, V, 1, 500000000LL) &amp;#x3C;&amp;#x3C; &quot;\\n&quot;;  // 8&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;code&gt;,     },     {       language: &quot;python&quot;,       label: &quot;Python&quot;,       code: &lt;/code&gt;# Dynamic Segment Tree - 딕셔너리 기반 (Python 특성 활용)
class DynamicSegTree:
def &lt;strong&gt;init&lt;/strong&gt;(self, lo, hi):
self.lo = lo
self.hi = hi
self.nodes = {}  # (lo, hi) -&gt; sum&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;def update(self, pos, val):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    self._update(self.lo, self.hi, pos, val)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;def query(self, ql, qr):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return self._query(self.lo, self.hi, ql, qr)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;def _update(self, lo, hi, pos, val):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    key = (lo, hi)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    self.nodes[key] = self.nodes.get(key, 0) + val&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if lo == hi:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    mid = (lo + hi) // 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if pos &amp;#x3C;= mid:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        self._update(lo, mid, pos, val)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    else:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        self._update(mid + 1, hi, pos, val)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;def _query(self, lo, hi, ql, qr):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    key = (lo, hi)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if key not in self.nodes:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        return 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if ql &amp;#x3C;= lo and hi &amp;#x3C;= qr:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        return self.nodes[key]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    mid = (lo + hi) // 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    res = 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if ql &amp;#x3C;= mid:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        res += self._query(lo, mid, ql, qr)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if qr &gt; mid:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        res += self._query(mid + 1, hi, ql, qr)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return res&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;사용-예시-범위-1-109&quot;&gt;사용 예시 (범위 [1, 10^9])&lt;/h1&gt;
&lt;p&gt;seg = DynamicSegTree(1, 10**9)
seg.update(100, 5)
seg.update(500_000_000, 3)
seg.update(900_000_000, 7)&lt;/p&gt;
&lt;p&gt;print(seg.query(1, 10**9))              # 15
print(seg.query(100, 500_000_000))      # 8
print(seg.query(1, 99))                 # 0
print(f”노드 수: {len(seg.nodes)}”)     # 약 3*30 = 90`,
},
]}
cases={[
{
label: “희소 업데이트 후 구간 합”,
input: “update(100,5), update(500000000,3), update(900000000,7)\nquery(1, 10^9)”,
output: “15”,
},
{
label: “부분 구간 쿼리”,
input: “query(100, 500000000)”,
output: “8”,
},
{
label: “빈 구간 쿼리”,
input: “query(1, 99)”,
output: “0”,
},
]}
/&gt;&lt;/p&gt;
&lt;h2 id=&quot;복잡도&quot;&gt;복잡도&lt;/h2&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;항목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;값&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Update&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(log V)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Query&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(log V)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;공간&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(Q log V)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Q = 총 쿼리 수, V = 값 범위. V = 10^9 이면 log V ≈ 30, Q = 2 x 10^5 이면 약 600만 노드.&lt;/p&gt;
&lt;h2 id=&quot;lazy-propagation-결합&quot;&gt;Lazy Propagation 결합&lt;/h2&gt;
&lt;p&gt;구간 업데이트 (범위 전체에 값 추가) 가 필요하면 lazy tag 추가:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; NodeL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    long&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; long&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sum &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, lazy &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    NodeL &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;l &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nullptr&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;r &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nullptr&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; push_down&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;NodeL&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; n&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;long&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; long&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; lo&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;long&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; long&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; hi&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;n-&gt;lazy) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    long&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; long&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; mid &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (lo &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; hi) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;n-&gt;l) n-&gt;l &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; NodeL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;n-&gt;r) n-&gt;r &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; NodeL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    n-&gt;l-&gt;sum &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n-&gt;lazy &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (mid &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; lo &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    n-&gt;l-&gt;lazy &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n-&gt;lazy;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    n-&gt;r-&gt;sum &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n-&gt;lazy &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (hi &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; mid);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    n-&gt;r-&gt;lazy &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n-&gt;lazy;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    n-&gt;lazy &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;함정&quot;&gt;함정&lt;/h2&gt;
&lt;h3 id=&quot;1-pool-크기-부족&quot;&gt;1. Pool 크기 부족&lt;/h3&gt;
&lt;p&gt;Q x log(V) 를 넉넉히 잡지 않으면 배열 초과. V = 10^9 이면 log_2 V ≈ 30, 구간 업데이트는 x2 필요.&lt;/p&gt;
&lt;h3 id=&quot;2-newdelete-반복-시-tle&quot;&gt;2. new/delete 반복 시 TLE&lt;/h3&gt;
&lt;p&gt;포인터 기반에서 매 쿼리마다 new 하면 할당자 오버헤드. Pool 방식 권장.&lt;/p&gt;
&lt;h3 id=&quot;3-lazy-push-시-nullptr-자식-생성-타이밍&quot;&gt;3. Lazy push 시 nullptr 자식 생성 타이밍&lt;/h3&gt;
&lt;p&gt;push_down 에서 nullptr 자식을 즉시 생성해야 lazy 값 전파 가능.&lt;/p&gt;
&lt;h3 id=&quot;4-pool-인덱스-0-충돌&quot;&gt;4. Pool 인덱스 0 충돌&lt;/h3&gt;
&lt;p&gt;Pool 기반에서 0 을 null 로 사용하고 실제 노드는 인덱스 1 부터. pcnt = 1 로 초기화.&lt;/p&gt;
&lt;h3 id=&quot;5-음수-좌표-범위&quot;&gt;5. 음수 좌표 범위&lt;/h3&gt;
&lt;p&gt;범위가 [-10^9, 10^9] 이면 lo, hi 타입을 long long 으로 선언. int 오버플로우 주의.&lt;/p&gt;
&lt;h2 id=&quot;대안-비교&quot;&gt;대안 비교&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;좌표 압축 + 일반 [[segtree|Segment Tree]]&lt;/strong&gt;: 오프라인 처리 가능하면 더 빠르고 단순&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;[[persistent-segtree|Persistent Segtree]]&lt;/strong&gt;: 버전 추적 (과거 상태 쿼리) 필요 시&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BIT (펜윅 트리)&lt;/strong&gt;: 점 업데이트 + 구간 합만 필요하면 구현 단순, 동적 버전은 복잡&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;boj-연습-문제&quot;&gt;BOJ 연습 문제&lt;/h2&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;번호&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;제목&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;정답률&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;링크&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 12899&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;데이터 구조&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;47.2%&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/kokoa-lab/boj-problems/tree/main/organize_problems/12800-12899/12899&quot;&gt;kokoa-lab&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 3653&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;영화 수집&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;42.3%&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/kokoa-lab/boj-problems/tree/main/organize_problems/3600-3699/3653&quot;&gt;kokoa-lab&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1168&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;요세푸스 문제 2&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;41.7%&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/kokoa-lab/boj-problems/tree/main/organize_problems/1100-1199/1168&quot;&gt;kokoa-lab&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[segtree|Segment Tree]]&lt;/li&gt;
&lt;li&gt;[[persistent-segtree|Persistent Segtree]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>algorithm</category><category>data-structure</category><category>segment-tree</category><author>koa (김신건)</author></item><item><title>Euler&apos;s Criterion: 이차잉여 판정</title><link>https://shinkeonkim.com/wiki/algorithm/math/eulers-criterion/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/algorithm/math/eulers-criterion/</guid><pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;홀소수 p에 대해 정수 a가 mod p에서 &lt;strong&gt;이차잉여 (Quadratic Residue, QR)&lt;/strong&gt; 인지 판정하는 기준:&lt;/p&gt;
&lt;p&gt;$$
a^{(p-1)/2} \equiv \begin{cases} 1 \pmod{p} &amp;#x26; \text{a가 mod p의 이차잉여} \ -1 \pmod{p} &amp;#x26; \text{a가 mod p의 비이차잉여} \end{cases}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Legendre 기호&lt;/strong&gt; $(a/p)$는 위 결과를 -1, 0, 1로 인코딩:&lt;/p&gt;
&lt;p&gt;$$
\left(\frac{a}{p}\right) = \begin{cases} 0 &amp;#x26; p \mid a \ 1 &amp;#x26; a \text{ is QR mod } p \ -1 &amp;#x26; a \text{ is NR mod } p \end{cases}
$$&lt;/p&gt;
&lt;h2 id=&quot;문제-상황&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p&gt;이차잉여 판정이 필요한 상황:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;제곱근 mod p 계산&lt;/strong&gt;: $x^2 \equiv a \pmod{p}$ 의 해 존재 여부 먼저 확인&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tonelli-Shanks 알고리즘&lt;/strong&gt;: QR 여부 판정 후 실제 제곱근 계산&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Solovay-Strassen 소수 판정&lt;/strong&gt;: Euler’s criterion을 합성수 판별에 활용&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;이차 상호 법칙&lt;/strong&gt;: Legendre 기호 계산 및 이차 방정식 mod p 풀이&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;시각화&quot;&gt;시각화&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    start[&quot;입력: 홀소수 p, 정수 a&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    zero[&quot;p 가 a 를 나누는가?&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    zero_yes[&quot;Legendre 기호 = 0&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    exp[&quot;거듭제곱 모듈러 연산 수행&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    one[&quot;결과 = 1 인가?&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    qr[&quot;이차잉여 QR: 제곱근 존재&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    nr[&quot;비이차잉여 NR: 제곱근 없음&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    start --&gt; zero&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    zero --&gt;|&quot;예&quot;| zero_yes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    zero --&gt;|&quot;아니오&quot;| exp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    exp --&gt; one&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    one --&gt;|&quot;= 1&quot;| qr&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    one --&gt;|&quot;= p-1&quot;| nr&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;핵심-아이디어&quot;&gt;핵심 아이디어&lt;/h2&gt;
&lt;h3 id=&quot;증명&quot;&gt;증명&lt;/h3&gt;
&lt;p&gt;[[flt|Fermat’s Little Theorem]]: $a^{p-1} \equiv 1 \pmod{p}$ (단, $p \nmid a$).&lt;/p&gt;
&lt;p&gt;따라서 $\left(a^{(p-1)/2}\right)^2 \equiv 1 \pmod{p}$, 즉 $a^{(p-1)/2} \equiv \pm 1 \pmod{p}$.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;QR 방향&lt;/strong&gt;: $a \equiv b^2 \pmod{p}$ 이면&lt;/p&gt;
&lt;p&gt;$$
a^{(p-1)/2} \equiv b^{p-1} \equiv 1 \pmod{p}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NR 방향&lt;/strong&gt;: $\mathbb{Z}_p^*$는 순환군 (원시근 g 존재). $a = g^k$로 쓰면:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;k가 짝수: $a = (g^{k/2})^2$ 이므로 QR, $a^{(p-1)/2} \equiv 1$&lt;/li&gt;
&lt;li&gt;k가 홀수: NR, $g^{(p-1)/2} \equiv -1$ (원시근 성질)이므로 $a^{(p-1)/2} \equiv -1$&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;p7-구체-예시&quot;&gt;p=7 구체 예시&lt;/h3&gt;
&lt;p&gt;p=7에서 $(p-1)/2 = 3$. 각 a에 대해 $a^3 \bmod 7$ 계산:&lt;/p&gt;















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;a&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;$a^3 \bmod 7$&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Legendre $(a/7)$&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;판정&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;QR ($1^2 \equiv 1$)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;2&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;QR ($3^2 \equiv 2$)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;3&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;6&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;-1&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;NR&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;4&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;QR ($2^2 \equiv 4$)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;5&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;6&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;-1&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;NR&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;6&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;6&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;-1&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;NR&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;p=7에서 QR: {1, 2, 4}, NR: {3, 5, 6}. QR 개수 = NR 개수 = (p-1)/2 = 3.&lt;/p&gt;
&lt;h3 id=&quot;legendre-기호-성질&quot;&gt;Legendre 기호 성질&lt;/h3&gt;
&lt;p&gt;$$
\left(\frac{ab}{p}\right) = \left(\frac{a}{p}\right)\left(\frac{b}{p}\right)
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;QR × QR = QR&lt;/li&gt;
&lt;li&gt;QR × NR = NR&lt;/li&gt;
&lt;li&gt;NR × NR = QR&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;알고리즘&quot;&gt;알고리즘&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Euler&apos;s Criterion: a가 mod p의 이차잉여인지 판정&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;function legendre(a, p):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    a = a mod p&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if a == 0: return 0          # Legendre 기호 = 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    result = pow(a, (p-1)//2, p)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if result == 1: return 1     # QR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    else: return -1              # NR (result == p-1)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;핵심: &lt;code&gt;pow(a, (p-1)//2, p)&lt;/code&gt; 는 빠른 거듭제곱으로 O(log p).&lt;/p&gt;
&lt;h2 id=&quot;구현&quot;&gt;구현&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
variants={[
{
language: “cpp”,
label: “C++”,
code: `#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;
typedef long long ll;&lt;/p&gt;
&lt;p&gt;// 빠른 거듭제곱 (모듈러)
ll power(ll base, ll exp, ll mod) {
ll result = 1;
base %= mod;
while (exp &gt; 0) {
if (exp &amp;#x26; 1) result = result * base % mod;
base = base * base % mod;
exp &gt;&gt;= 1;
}
return result;
}&lt;/p&gt;
&lt;p&gt;// Euler’s Criterion: Legendre 기호 반환 (-1, 0, 1)
int legendre(ll a, ll p) {
a %= p;
if (a &amp;#x3C; 0) a += p;
if (a == 0) return 0;
ll val = power(a, (p - 1) / 2, p);
return (val == 1) ? 1 : -1;
}&lt;/p&gt;
&lt;p&gt;int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ll a, p;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cin &gt;&gt; a &gt;&gt; p;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;int result = legendre(a, p);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;if (result == 0)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    cout &amp;#x3C;&amp;#x3C; a &amp;#x3C;&amp;#x3C; &quot; is divisible by &quot; &amp;#x3C;&amp;#x3C; p &amp;#x3C;&amp;#x3C; &quot;\\n&quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;else if (result == 1)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    cout &amp;#x3C;&amp;#x3C; a &amp;#x3C;&amp;#x3C; &quot; is a quadratic residue mod &quot; &amp;#x3C;&amp;#x3C; p &amp;#x3C;&amp;#x3C; &quot;\\n&quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;else&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    cout &amp;#x3C;&amp;#x3C; a &amp;#x3C;&amp;#x3C; &quot; is a non-residue mod &quot; &amp;#x3C;&amp;#x3C; p &amp;#x3C;&amp;#x3C; &quot;\\n&quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;return 0;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;code&gt;,     },     {       language: &quot;python&quot;,       label: &quot;Python&quot;,       code: &lt;/code&gt;def legendre(a, p):
&quot;&quot;&quot;Euler’s Criterion: Legendre 기호 반환 (-1, 0, 1)&quot;&quot;&quot;
a %= p
if a == 0:
return 0
val = pow(a, (p - 1) // 2, p)
return 1 if val == 1 else -1&lt;/p&gt;
&lt;p&gt;def main():
a, p = map(int, input().split())
result = legendre(a, p)
if result == 0:
print(f”{a} is divisible by {p}”)
elif result == 1:
print(f”{a} is a quadratic residue mod {p}”)
else:
print(f”{a} is a non-residue mod {p}”)&lt;/p&gt;
&lt;p&gt;main()&lt;code&gt;,     },   ]}   cases={[     {       label: &quot;p=7, a=2 (QR)&quot;,       input: &lt;/code&gt;2 7&lt;code&gt;,       output: &lt;/code&gt;2 is a quadratic residue mod 7&lt;code&gt;,     },     {       label: &quot;p=7, a=3 (NR)&quot;,       input: &lt;/code&gt;3 7&lt;code&gt;,       output: &lt;/code&gt;3 is a non-residue mod 7&lt;code&gt;,     },     {       label: &quot;p=7, a=7 (0)&quot;,       input: &lt;/code&gt;7 7&lt;code&gt;,       output: &lt;/code&gt;7 is divisible by 7`,
},
]}
/&gt;&lt;/p&gt;
&lt;h2 id=&quot;복잡도&quot;&gt;복잡도&lt;/h2&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;항목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;값&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;시간&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(log p)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;공간&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(1)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;핵심 연산&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;빠른 거듭제곱 (모듈러 지수)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;응용&quot;&gt;응용&lt;/h2&gt;
&lt;h3 id=&quot;1-tonelli-shanks-알고리즘&quot;&gt;1. Tonelli-Shanks 알고리즘&lt;/h3&gt;
&lt;p&gt;$x^2 \equiv a \pmod{p}$ 의 실제 해를 구하는 알고리즘. Euler’s criterion으로 QR 여부 먼저 확인 후 실행.&lt;/p&gt;
&lt;p&gt;[[discrete-sqrt|Tonelli-Shanks]] 참조.&lt;/p&gt;
&lt;h3 id=&quot;2-solovay-strassen-소수-판정&quot;&gt;2. Solovay-Strassen 소수 판정&lt;/h3&gt;
&lt;p&gt;합성수 n에 대해 $a^{(n-1)/2} \not\equiv (a/n) \pmod{n}$ 인 a가 존재하면 n은 합성수. 확률적 소수 판정에 활용.&lt;/p&gt;
&lt;h3 id=&quot;3-jacobi-기호-일반화&quot;&gt;3. Jacobi 기호 (일반화)&lt;/h3&gt;
&lt;p&gt;Legendre 기호를 홀수 합성수 n으로 확장:&lt;/p&gt;
&lt;p&gt;$$
\left(\frac{a}{n}\right) = \prod_{i} \left(\frac{a}{p_i}\right)^{e_i} \quad (n = \prod p_i^{e_i})
$$&lt;/p&gt;
&lt;p&gt;Jacobi 기호가 -1이면 반드시 NR. 1이면 QR이거나 합성수.&lt;/p&gt;
&lt;h3 id=&quot;4-이차-방정식-mod-p&quot;&gt;4. 이차 방정식 mod p&lt;/h3&gt;
&lt;p&gt;$ax^2 + bx + c \equiv 0 \pmod{p}$ 의 판별식 $\Delta = b^2 - 4ac$:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$(\Delta/p) = 1$: 두 해 존재&lt;/li&gt;
&lt;li&gt;$(\Delta/p) = 0$: 중근&lt;/li&gt;
&lt;li&gt;$(\Delta/p) = -1$: 해 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;함정&quot;&gt;함정&lt;/h2&gt;
&lt;h3 id=&quot;1-p가-소수가-아닌-경우&quot;&gt;1. p가 소수가 아닌 경우&lt;/h3&gt;
&lt;p&gt;Euler’s criterion은 &lt;strong&gt;홀소수 p에서만&lt;/strong&gt; 성립. 합성수에서는 Jacobi 기호를 사용.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
p가 합성수일 때 $a^{(p-1)/2} \equiv 1 \pmod{p}$ 이어도 QR이 아닐 수 있음.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;2-a0-처리-누락&quot;&gt;2. a=0 처리 누락&lt;/h3&gt;
&lt;p&gt;$p \mid a$ 이면 Legendre 기호 = 0. 이 경우를 별도 처리하지 않으면 오답.&lt;/p&gt;
&lt;h3 id=&quot;3-결과-p-1과--1-혼동&quot;&gt;3. 결과 p-1과 -1 혼동&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;pow(a, (p-1)/2, p)&lt;/code&gt; 의 결과가 &lt;code&gt;p-1&lt;/code&gt;이면 이는 $-1 \pmod{p}$. NR 판정.&lt;/p&gt;
&lt;h3 id=&quot;4-정수-오버플로&quot;&gt;4. 정수 오버플로&lt;/h3&gt;
&lt;p&gt;C++에서 &lt;code&gt;a * a % p&lt;/code&gt; 계산 시 &lt;code&gt;a&lt;/code&gt;가 &lt;code&gt;int&lt;/code&gt; 범위이면 &lt;code&gt;a * a&lt;/code&gt;가 오버플로. &lt;code&gt;long long&lt;/code&gt; 사용 필수.&lt;/p&gt;
&lt;h3 id=&quot;5-음수-a-처리&quot;&gt;5. 음수 a 처리&lt;/h3&gt;
&lt;p&gt;a가 음수이면 &lt;code&gt;a %= p&lt;/code&gt; 후 음수가 될 수 있음. &lt;code&gt;if (a &amp;#x3C; 0) a += p;&lt;/code&gt; 추가 필요.&lt;/p&gt;
&lt;h2 id=&quot;boj-연습-문제&quot;&gt;BOJ 연습 문제&lt;/h2&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;번호&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;제목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;관련 개념&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 11401&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;이항 계수 3&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;모듈러 역원 (FLT 응용)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 13977&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;이항 계수와 쿼리&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;모듈러 지수&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 17831&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;이차잉여&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Euler’s criterion 직접 적용&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[flt|Fermat’s Little Theorem]]&lt;/li&gt;
&lt;li&gt;[[discrete-sqrt|Tonelli-Shanks]]&lt;/li&gt;
&lt;li&gt;[[quadratic-residue|Quadratic Residue]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>algorithm</category><category>math</category><category>number-theory</category><category>quadratic-residue</category><author>koa (김신건)</author></item><item><title>Expected Value: 기댓값, Linearity</title><link>https://shinkeonkim.com/wiki/algorithm/foundation/expected-value/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/algorithm/foundation/expected-value/</guid><pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;확률변수 X 의 기댓값:&lt;/p&gt;
&lt;p&gt;$$
E[X] = \sum_x x \cdot P(X = x) \quad \text{(discrete)}
$$&lt;/p&gt;
&lt;p&gt;$$
E[X] = \int x \cdot f(x) , dx \quad \text{(continuous)}
$$&lt;/p&gt;
&lt;p&gt;기댓값은 “평균적으로 기대되는 값”. 랜덤 알고리즘 분석, 확률 DP 문제에서 핵심 도구.&lt;/p&gt;
&lt;h2 id=&quot;문제-상황&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p&gt;복잡한 확률 공간에서 랜덤 변수의 기댓값을 구해야 할 때, 직접 계산하면 경우의 수가 폭발적으로 늘어난다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt;: n 개 원소의 랜덤 순열에서 고정점 (자기 자리에 오는 원소, fixed point) 의 평균 개수?&lt;/p&gt;
&lt;p&gt;순진한 접근: 고정점이 정확히 k 개인 경우를 모두 열거. 포함-배제 원리 필요. 복잡함.&lt;/p&gt;
&lt;p&gt;핵심 아이디어: &lt;strong&gt;지시 변수 (indicator variable)&lt;/strong&gt; 로 분해 + &lt;strong&gt;Linearity of Expectation&lt;/strong&gt; 적용.&lt;/p&gt;
&lt;h2 id=&quot;시각화&quot;&gt;시각화&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    R[&quot;랜덤 변수 X 계산&quot;] --&gt;|&quot;직접 계산&quot;| D[&quot;경우의 수 폭발&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    R --&gt;|&quot;분해 접근&quot;| L[&quot;지시 변수 Xi 합으로 표현&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    L --&gt; Ei[&quot;각 E_Xi 개별 계산&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Ei --&gt; S[&quot;Linearity 적용해 합산&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    style D fill:#f9a825,color:#000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    style S fill:#2e7d32,color:#fff&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;핵심-아이디어&quot;&gt;핵심 아이디어&lt;/h2&gt;
&lt;h3 id=&quot;linearity-of-expectation&quot;&gt;Linearity of Expectation&lt;/h3&gt;
&lt;p&gt;$$
E[X + Y] = E[X] + E[Y]
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;독립 여부 무관&lt;/strong&gt;. X, Y 가 상관관계가 있어도 성립한다.&lt;/p&gt;
&lt;p&gt;이것이 핵심: 복잡한 합 확률 변수를 단순한 지시 변수들의 합으로 분해 가능.&lt;/p&gt;
&lt;h3 id=&quot;지시-변수&quot;&gt;지시 변수&lt;/h3&gt;
&lt;p&gt;지시 변수 $X_i$:&lt;/p&gt;
&lt;p&gt;$$
X_i = \begin{cases} 1 &amp;#x26; \text{이벤트 } i \text{ 발생} \ 0 &amp;#x26; \text{아닐 때} \end{cases}
$$&lt;/p&gt;
&lt;p&gt;$$
E[X_i] = 1 \cdot P(\text{이벤트}_i) + 0 \cdot P(\lnot\text{이벤트}_i) = P(\text{이벤트}_i)
$$&lt;/p&gt;
&lt;h3 id=&quot;분해-패턴&quot;&gt;분해 패턴&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;원하는 값 X 를 지시 변수 합으로 표현: $X = \sum_{i} X_i$&lt;/li&gt;
&lt;li&gt;Linearity 적용: $E[X] = \sum_{i} E[X_i]$&lt;/li&gt;
&lt;li&gt;각 $E[X_i] = P(\text{이벤트}_i)$ 를 개별 계산&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;각 $P(\text{이벤트}_i)$ 는 원래 문제보다 훨씬 단순한 경우가 많다.&lt;/p&gt;
&lt;h2 id=&quot;알고리즘&quot;&gt;알고리즘&lt;/h2&gt;
&lt;h3 id=&quot;예시-1-fixed-points-in-random-permutation&quot;&gt;예시 1: Fixed Points in Random Permutation&lt;/h3&gt;
&lt;p&gt;n 개의 원소를 랜덤 순열 배치, 고정점 개수의 기댓값?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$X_i$: i번째 원소가 고정점이면 1, 아니면 0&lt;/li&gt;
&lt;li&gt;$P(X_i = 1) = \frac{1}{n}$ (n 자리 중 자기 자리에 올 확률)&lt;/li&gt;
&lt;li&gt;Linearity 적용:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
E[X] = \sum_{i=1}^{n} E[X_i] = \sum_{i=1}^{n} \frac{1}{n} = 1
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;랜덤 순열의 고정점 기댓값 = 1&lt;/strong&gt; (n 에 무관).&lt;/p&gt;
&lt;h3 id=&quot;예시-2-coupon-collector&quot;&gt;예시 2: Coupon Collector&lt;/h3&gt;
&lt;p&gt;n 종류 쿠폰, 매번 1개 랜덤 수집. 전 종류 모으려면 평균 몇 번?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;k-1 종류 보유 중, 새 쿠폰 확률 = $\frac{n - k + 1}{n}$&lt;/li&gt;
&lt;li&gt;k 번째 새 쿠폰 수집에 필요한 시도 횟수 기댓값:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
E[T_k] = \frac{n}{n - k + 1}
$$&lt;/p&gt;
&lt;p&gt;$$
E[\text{total}] = \sum_{k=1}^{n} \frac{n}{n-k+1} = n \cdot H_n \approx n \ln n
$$&lt;/p&gt;
&lt;p&gt;여기서 $H_n = 1 + \frac{1}{2} + \ldots + \frac{1}{n}$ 는 조화급수.&lt;/p&gt;
&lt;h3 id=&quot;예시-3-quicksort-평균-비교-횟수&quot;&gt;예시 3: Quicksort 평균 비교 횟수&lt;/h3&gt;
&lt;p&gt;임의 pivot 선택 quicksort 의 평균 비교 횟수.&lt;/p&gt;
&lt;p&gt;$X_{ij}$: 원소 i 와 j 가 비교되면 1, 아니면 0 ($i &amp;#x3C; j$).&lt;/p&gt;
&lt;p&gt;$P(X_{ij} = 1) = \frac{2}{j - i + 1}$ (i..j 범위에서 i 또는 j 가 먼저 pivot 으로 선택될 확률).&lt;/p&gt;
&lt;p&gt;$$
E[\text{비교 횟수}] = \sum_{i &amp;#x3C; j} \frac{2}{j-i+1} = O(N \log N)
$$&lt;/p&gt;
&lt;h2 id=&quot;구현&quot;&gt;구현&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
variants={[
{
language: “python”,
label: “Python: Simulation 검증”,
code: `import random&lt;/p&gt;
&lt;p&gt;def count_fixed_points(perm):
return sum(1 for i, x in enumerate(perm) if i == x)&lt;/p&gt;
&lt;p&gt;def coupon_steps(n, trials=50000):
total = 0
for _ in range(trials):
collected = set()
steps = 0
while len(collected) &amp;#x3C; n:
collected.add(random.randint(0, n - 1))
steps += 1
total += steps
return total / trials&lt;/p&gt;
&lt;h1 id=&quot;고정점-기댓값--1-검증&quot;&gt;고정점 기댓값 = 1 검증&lt;/h1&gt;
&lt;p&gt;n = 8
trials = 50000
random.seed(42)
fp_total = 0
for _ in range(trials):
perm = list(range(n))
random.shuffle(perm)
fp_total += count_fixed_points(perm)
print(“Fixed points E =”, round(fp_total / trials, 3))  # ~1.0&lt;/p&gt;
&lt;h1 id=&quot;coupon-collector-et--n--h_n&quot;&gt;Coupon Collector E[T] ~ n * H_n&lt;/h1&gt;
&lt;p&gt;n_c = 5
sim = coupon_steps(n_c)
theory = n_c * sum(1.0 / (n_c - k + 1) for k in range(1, n_c + 1))
print(“Coupon n=5 sim=”, round(sim, 2), “theory=”, round(theory, 2))&lt;code&gt;,     },     {       language: &quot;cpp&quot;,       label: &quot;C++: Simulation&quot;,       code: &lt;/code&gt;#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;&lt;/p&gt;
&lt;p&gt;int main() {
mt19937 rng(42);
int n = 8, trials = 50000;
double fp = 0;
vector&lt;int&gt; perm(n);
iota(perm.begin(), perm.end(), 0);
for (int t = 0; t &amp;#x3C; trials; t++) {
shuffle(perm.begin(), perm.end(), rng);
for (int i = 0; i &amp;#x3C; n; i++) fp += (perm[i] == i);
}
printf(“Fixed points E = %.3f\n”, fp / trials);  // ~1.0&lt;/int&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// Coupon Collector&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;int nc = 5;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;double theory = 0;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for (int k = 1; k &amp;#x3C;= nc; k++) theory += (double)nc / (nc - k + 1);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;printf(&quot;Coupon n=%d theory=%.2f\\n&quot;, nc, theory);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;return 0;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;code&gt;,     },   ]}   cases={[     {       label: &quot;기본&quot;,       input: ``,       output: &lt;/code&gt;Fixed points E = 1.000
Coupon n=5 sim=11.41 theory=11.42`,
},
]}
/&gt;&lt;/p&gt;
&lt;h2 id=&quot;복잡도&quot;&gt;복잡도&lt;/h2&gt;
&lt;p&gt;기댓값 계산은 분석 도구이지, 알고리즘 자체가 아님. 실제 알고리즘 복잡도에 자주 등장하는 공식:&lt;/p&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;문제 유형&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;기댓값 공식&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;비고&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Fixed points in permutation&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;$E = 1$&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;n 무관&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Coupon Collector&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;$E = n \cdot H_n \approx n \ln n$&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;조화급수&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Quicksort 평균 비교 횟수&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;$O(N \log N)$&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Linearity 분해&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Randomized Select&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;$O(N)$ expected&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;기하 분포 성공까지 횟수&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;$E = 1/p$&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;p: 성공 확률&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Randomized Primality&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;$O(k \log N)$&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;k: 반복 횟수&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;함정&quot;&gt;함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
&lt;strong&gt;비선형 함수에는 Linearity 없음&lt;/strong&gt;: $E[X^2] \neq E[X]^2$. 일반적으로 $E[f(X)] \neq f(E[X])$ (Jensen’s inequality).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
&lt;strong&gt;곱셈&lt;/strong&gt;: $E[XY] = E[X] \cdot E[Y]$ 는 독립일 때만 성립. 상관 있으면 공분산 항 추가됨.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
기댓값이 &lt;strong&gt;발산&lt;/strong&gt;할 수 있다. St. Petersburg paradox: 무한 기댓값. 무한 합 수렴 여부를 반드시 확인.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;흔한-실수&quot;&gt;흔한 실수&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;$E[XY] \neq E[X] \cdot E[Y]$ 를 독립 확인 없이 적용&lt;/li&gt;
&lt;li&gt;조건부 기댓값 $E[X \mid A]$ 와 $E[X]$ 혼동&lt;/li&gt;
&lt;li&gt;지시 변수로 분해 시 이벤트 겹침 (overlap) 고려 안 함&lt;/li&gt;
&lt;li&gt;연속 분포에서 합산을 적분으로 바꾸지 않음&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;boj-연습-문제&quot;&gt;BOJ 연습 문제&lt;/h2&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;번호&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;제목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;키워드&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 11590&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;기댓값&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;기댓값 직접 계산&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 14788&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;슬라임 연구자&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;기댓값 DP&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 2062&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;돌멩이 제거&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;기댓값 + 확률&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 17404&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;RGB 거리 2&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;확률 응용&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 15810&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;풍선 공장&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;이분 탐색 + 기댓값&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[probability|확률론 기초]]&lt;/li&gt;
&lt;li&gt;[[markov-chain|Markov Chain]]&lt;/li&gt;
&lt;li&gt;[[randomization|랜덤화 알고리즘]]&lt;/li&gt;
&lt;li&gt;[[bayes|베이즈 정리]]&lt;/li&gt;
&lt;li&gt;[[birthday|Birthday Problem]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>algorithm</category><category>foundation</category><category>probability</category><category>math</category><author>koa (김신건)</author></item><item><title>Golden-section Search: 단봉 함수 최적화</title><link>https://shinkeonkim.com/wiki/algorithm/math/golden-section-search/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/algorithm/math/golden-section-search/</guid><pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;단봉 함수 (Unimodal function)&lt;/strong&gt; 위에서 최소/최대를 찾는 탐색 기법. 이분 탐색을 단봉 조건으로 확장한 알고리즘입니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;삼분 탐색 (Ternary Search)&lt;/strong&gt;: 구간을 3등분, 매 반복 함수 &lt;strong&gt;2회&lt;/strong&gt; 호출, 1/3 제거&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;황금 분할 탐색 (Golden-section Search)&lt;/strong&gt;: 황금비로 분점, 매 반복 함수 &lt;strong&gt;1회&lt;/strong&gt; 호출, 이전 값 재사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;단봉 함수 조건: 구간 [l, r] 에서 유일한 최대 (혹은 최소) 점 m 이 존재하고, m 의 왼쪽은 단조 증가, 오른쪽은 단조 감소 (최대화 기준).&lt;/p&gt;
&lt;h2 id=&quot;문제-상황과-동기&quot;&gt;문제 상황과 동기&lt;/h2&gt;
&lt;h3 id=&quot;이분-탐색으로-못-쓰는-이유&quot;&gt;이분 탐색으로 못 쓰는 이유&lt;/h3&gt;
&lt;p&gt;이분 탐색은 단조 함수 전제. 단봉 함수는 한 번 증가했다 감소하므로 “왼쪽이 작으면 오른쪽에 답이 있다”가 성립하지 않습니다.&lt;/p&gt;
&lt;p&gt;단봉 함수 예시:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$f(x) = -(x-3)^2 + 10$ (최대 x=3)&lt;/li&gt;
&lt;li&gt;볼록 다각형에서 특정 방향 최대 좌표&lt;/li&gt;
&lt;li&gt;구간합의 최대 연속 부분합 (Slope Trick 형태)&lt;/li&gt;
&lt;li&gt;기하 최적화: 점과 함수 곡선 간 최단 거리&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;언제-사용하는가&quot;&gt;언제 사용하는가&lt;/h3&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;조건&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;사용 여부&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;함수가 단봉 (unimodal) 임이 명확&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✅ 사용&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;구간 [l, r] 이 연속 실수&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✅ 삼분 / 황금 분할&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;구간이 정수 이산&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✅ 정수 삼분 탐색&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;함수가 볼록/오목 (convex/concave)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;[[cht&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;시각화&quot;&gt;시각화&lt;/h2&gt;
&lt;h3 id=&quot;삼분-탐색-구간-3등분-후-13-제거&quot;&gt;삼분 탐색: 구간 3등분 후 1/3 제거&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    L[&quot;l&quot;] --&gt; M1[&quot;m1 = l + (r-l)/3&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    M1 --&gt; M2[&quot;m2 = r - (r-l)/3&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    M2 --&gt; R[&quot;r&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Cmp[&quot;f(m1) vs f(m2) 비교&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    M1 --&gt; Cmp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    M2 --&gt; Cmp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Cmp --&gt;|&quot;f(m1) 작을 때 (최대화)&quot;| NewR[&quot;r = m2 (오른쪽 1/3 유지)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Cmp --&gt;|&quot;f(m2) 작을 때 (최대화)&quot;| NewL[&quot;l = m1 (왼쪽 1/3 유지)&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;매 반복: 구간이 2/3 로 줄어듭니다. 100회 반복 시 구간 길이 $(2/3)^{100} \approx 2 \times 10^{-18}$.&lt;/p&gt;
&lt;h3 id=&quot;황금-분할-이전-분점-재사용&quot;&gt;황금 분할: 이전 분점 재사용&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Step1[&quot;1번째 반복: m1, m2 계산 (f 2회 호출)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Step2[&quot;m1 쪽 제거 결정 -&gt; 새 구간 [m1, r]&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Step3[&quot;2번째 반복: 이전 m2 가 새 m1 역할 (재사용!)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Step4[&quot;새 m2 만 추가 계산 (f 1회 호출)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Step1 --&gt; Step2 --&gt; Step3 --&gt; Step4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;황금비 $\varphi = \frac{1+\sqrt{5}}{2} \approx 1.618$, $1/\varphi \approx 0.618$.&lt;/p&gt;
&lt;p&gt;분점 위치: $m_1 = r - \frac{1}{\varphi}(r-l)$, $m_2 = l + \frac{1}{\varphi}(r-l)$.&lt;/p&gt;
&lt;p&gt;$m_2$ 를 버린 경우, 새 구간 $[l, m_2]$ 에서 이전 $m_1$ 이 정확히 새 구간의 황금비 위치가 됩니다. 따라서 $f(m_1)$ 재사용, 새 $m_2$ 만 계산.&lt;/p&gt;
&lt;h2 id=&quot;핵심-아이디어&quot;&gt;핵심 아이디어&lt;/h2&gt;
&lt;h3 id=&quot;삼분-탐색-ternary-search&quot;&gt;삼분 탐색 (Ternary Search)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;최대화 기준&lt;/strong&gt;: $f(m_1) &amp;#x3C; f(m_2)$ 이면 최대점은 $m_1$ 오른쪽에 있으므로 $l = m_1$ (왼쪽 1/3 제거). 반대면 $r = m_2$.&lt;/p&gt;
&lt;p&gt;수렴율: 매 반복마다 구간 1/3 제거 → 같은 정밀도에 삼분은 더 많은 반복 필요.&lt;/p&gt;
&lt;h3 id=&quot;황금-분할-탐색-golden-section-search&quot;&gt;황금 분할 탐색 (Golden-section Search)&lt;/h3&gt;
&lt;p&gt;수렴율: 매 반복마다 구간의 $1 - 1/\varphi \approx 38.2%$ 제거. 삼분 탐색의 $33.3%$ 보다 더 작은 제거율이지만, &lt;strong&gt;함수 호출이 절반&lt;/strong&gt; 이라 동일 호출 횟수 대비 더 빠른 수렴.&lt;/p&gt;























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;기법&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;반복당 호출&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;반복당 제거 비율&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;n회 호출 후 정밀도&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;삼분 탐색&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;2&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;33.3%&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;$(2/3)^{n/2}$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;황금 분할&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1 (이후)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;38.2%&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;$(1/\varphi)^{n-1}$&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;정수-삼분-탐색&quot;&gt;정수 삼분 탐색&lt;/h3&gt;
&lt;p&gt;이산 도메인 (정수 인덱스) 에서는 &lt;code&gt;while r - l &gt; 2&lt;/code&gt; 종료, 나머지 구간 완전 탐색.&lt;/p&gt;
&lt;h2 id=&quot;알고리즘&quot;&gt;알고리즘&lt;/h2&gt;
&lt;h3 id=&quot;실수-삼분-탐색-최대화&quot;&gt;실수 삼분 탐색 (최대화)&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ternary_max(l, r, f, iter=200):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    repeat iter times:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        m1 = l + (r - l) / 3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        m2 = r - (r - l) / 3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        if f(m1) &amp;#x3C; f(m2): l = m1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        else: r = m2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return (l + r) / 2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;황금-분할-탐색-최대화&quot;&gt;황금 분할 탐색 (최대화)&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;golden_max(l, r, f):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    phi_inv = (sqrt(5) - 1) / 2  # 1/phi ≈ 0.618&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    m1 = r - phi_inv * (r - l)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    m2 = l + phi_inv * (r - l)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    f1, f2 = f(m1), f(m2)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    repeat 200 times:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        if f1 &amp;#x3C; f2:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            l = m1; m1 = m2; f1 = f2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            m2 = l + phi_inv * (r - l); f2 = f(m2)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        else:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            r = m2; m2 = m1; f2 = f1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            m1 = r - phi_inv * (r - l); f1 = f(m1)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return (l + r) / 2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;정수-삼분-탐색-1&quot;&gt;정수 삼분 탐색&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;int_ternary_max(l, r, g):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    while r - l &gt; 2:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        m1 = l + (r - l) / 3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        m2 = r - (r - l) / 3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        if g(m1) &amp;#x3C; g(m2): l = m1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        else: r = m2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return argmax(g, l..r)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;구현&quot;&gt;구현&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
variants={[
{
language: “cpp”,
label: “C++”,
code: `// Golden-section Search / Ternary Search
#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;&lt;/p&gt;
&lt;p&gt;// 예시 함수: 최대 x=3 에서 f=10
auto f = [](double x) { return -(x-3)&lt;em&gt;(x-3) + 10; };
// 정수 예시: 최대 x=5 에서 g=25
auto g = [](int x) { return -(x-5)&lt;/em&gt;(x-5) + 25; };&lt;/p&gt;
&lt;p&gt;// 삼분 탐색 (최대화)
double ternary_max(double l, double r, int iter = 200) {
for (int i = 0; i &amp;#x3C; iter; i++) {
double m1 = l + (r - l) / 3;
double m2 = r - (r - l) / 3;
if (f(m1) &amp;#x3C; f(m2)) l = m1;
else r = m2;
}
return (l + r) / 2;
}&lt;/p&gt;
&lt;p&gt;// 황금 분할 탐색 (최대화)
double golden_max(double l, double r) {
const double phi = (sqrt(5.0) - 1.0) / 2.0;  // 1/phi ≈ 0.618
double m1 = r - phi * (r - l);
double m2 = l + phi * (r - l);
double f1 = f(m1), f2 = f(m2);
for (int i = 0; i &amp;#x3C; 200; i++) {
if (f1 &amp;#x3C; f2) {
l = m1; m1 = m2; f1 = f2;
m2 = l + phi * (r - l); f2 = f(m2);
} else {
r = m2; m2 = m1; f2 = f1;
m1 = r - phi * (r - l); f1 = f(m1);
}
}
return (l + r) / 2;
}&lt;/p&gt;
&lt;p&gt;// 정수 삼분 탐색 (최대화)
int int_ternary_max(int l, int r) {
while (r - l &gt; 2) {
int m1 = l + (r - l) / 3;
int m2 = r - (r - l) / 3;
if (g(m1) &amp;#x3C; g(m2)) l = m1;
else r = m2;
}
int best = l;
for (int x = l+1; x &amp;#x3C;= r; x++)
if (g(x) &gt; g(best)) best = x;
return best;
}&lt;/p&gt;
&lt;p&gt;int main() {
cout &amp;#x3C;&amp;#x3C; fixed &amp;#x3C;&amp;#x3C; setprecision(8);
double xT = ternary_max(0.0, 10.0);
cout &amp;#x3C;&amp;#x3C; “삼분 탐색 최대 위치: ” &amp;#x3C;&amp;#x3C; xT &amp;#x3C;&amp;#x3C; ”  f=” &amp;#x3C;&amp;#x3C; f(xT) &amp;#x3C;&amp;#x3C; “\n”;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;double xG = golden_max(0.0, 10.0);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cout &amp;#x3C;&amp;#x3C; &quot;황금 분할 최대 위치: &quot; &amp;#x3C;&amp;#x3C; xG &amp;#x3C;&amp;#x3C; &quot;  f=&quot; &amp;#x3C;&amp;#x3C; f(xG) &amp;#x3C;&amp;#x3C; &quot;\\n&quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;int xI = int_ternary_max(0, 10);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cout &amp;#x3C;&amp;#x3C; &quot;정수 삼분 최대 위치: &quot; &amp;#x3C;&amp;#x3C; xI &amp;#x3C;&amp;#x3C; &quot;  g=&quot; &amp;#x3C;&amp;#x3C; g(xI) &amp;#x3C;&amp;#x3C; &quot;\\n&quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;return 0;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;code&gt;,     },     {       language: &quot;python&quot;,       label: &quot;Python&quot;,       code: &lt;/code&gt;import math&lt;/p&gt;
&lt;p&gt;def f(x): return -(x-3)**2 + 10   # 최대: x=3, f=10
def g(x): return -(x-5)**2 + 25   # 정수 최대: x=5, g=25&lt;/p&gt;
&lt;p&gt;def ternary_max(l, r, iters=200):
&quot;&quot;&quot;삼분 탐색 (최대화)&quot;&quot;&quot;
for _ in range(iters):
m1 = l + (r - l) / 3
m2 = r - (r - l) / 3
if f(m1) &amp;#x3C; f(m2):
l = m1
else:
r = m2
return (l + r) / 2&lt;/p&gt;
&lt;p&gt;def golden_max(l, r):
&quot;&quot;&quot;황금 분할 탐색 (최대화) - 함수 호출 절반&quot;&quot;&quot;
phi = (math.sqrt(5) - 1) / 2   # 1/phi ≈ 0.618
m1 = r - phi * (r - l)
m2 = l + phi * (r - l)
f1, f2 = f(m1), f(m2)
for _ in range(200):
if f1 &amp;#x3C; f2:
l, m1, f1 = m1, m2, f2
m2 = l + phi * (r - l)
f2 = f(m2)
else:
r, m2, f2 = m2, m1, f1
m1 = r - phi * (r - l)
f1 = f(m1)
return (l + r) / 2&lt;/p&gt;
&lt;p&gt;def int_ternary_max(l, r):
&quot;&quot;&quot;정수 삼분 탐색 (최대화)&quot;&quot;&quot;
while r - l &gt; 2:
m1 = l + (r - l) // 3
m2 = r - (r - l) // 3
if g(m1) &amp;#x3C; g(m2):
l = m1
else:
r = m2
return max(range(l, r+1), key=g)&lt;/p&gt;
&lt;p&gt;xT = ternary_max(0, 10)
print(f”삼분 탐색 최대 위치: {xT:.8f}  f={f(xT):.4f}”)&lt;/p&gt;
&lt;p&gt;xG = golden_max(0, 10)
print(f”황금 분할 최대 위치: {xG:.8f}  f={f(xG):.4f}”)&lt;/p&gt;
&lt;p&gt;xI = int_ternary_max(0, 10)
print(f”정수 삼분 최대 위치: {xI}  g={g(xI)}”)`,
},
]}
cases={[
{
label: “실수 최대화 (x=3 에서 f=10)”,
input: “f(x) = -(x-3)^2 + 10, 구간 [0, 10]”,
output: “삼분 탐색 최대 위치: 3.00000000  f=10.0000\n황금 분할 최대 위치: 3.00000000  f=10.0000”,
},
{
label: “정수 최대화 (x=5 에서 g=25)”,
input: “g(x) = -(x-5)^2 + 25, 구간 [0, 10]”,
output: “정수 삼분 최대 위치: 5  g=25”,
},
]}
/&gt;&lt;/p&gt;
&lt;h2 id=&quot;복잡도&quot;&gt;복잡도&lt;/h2&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;항목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;삼분 탐색&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;황금 분할&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;반복당 f 호출&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;2회&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;1회 (이후)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;n회 f 호출 후 정밀도&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;$(2/3)^{n/2} \cdot (r-l)$&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;$(1/\varphi)^{n-1} \cdot (r-l)$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;100회 호출 후&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;$(2/3)^{50} \approx 1.2 \times 10^{-9}$&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;$(0.618)^{99} \approx 2.2 \times 10^{-21}$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;iter=100 삼분&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;정밀도 $10^{-17}$ (200 호출)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;황금 분할이 같은 f 호출 횟수로 훨씬 높은 정밀도.&lt;/p&gt;
&lt;h2 id=&quot;함정&quot;&gt;함정&lt;/h2&gt;
&lt;h3 id=&quot;1-단봉-조건-미확인&quot;&gt;1. 단봉 조건 미확인&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
함수가 단봉이 아닌 경우 (예: 여러 개의 극값) 삼분 탐색은 잘못된 결과를 냅니다. 문제에서 “단봉” 또는 “볼록” 조건을 반드시 확인하세요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;2-최솟값-vs-최댓값-혼동&quot;&gt;2. 최솟값 vs 최댓값 혼동&lt;/h3&gt;
&lt;p&gt;최솟값: &lt;code&gt;f(m1) &gt; f(m2)&lt;/code&gt; 이면 &lt;code&gt;l = m1&lt;/code&gt;. 최댓값과 조건이 반대.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 최솟값&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;f&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(m1) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; f&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(m2)) l &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; m1;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; m2;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 최댓값&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;f&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(m1) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; f&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(m2)) l &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; m1;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; m2;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;3-정수-삼분-탐색에서-종료-조건-오류&quot;&gt;3. 정수 삼분 탐색에서 종료 조건 오류&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;while r - l &gt; 2&lt;/code&gt; 가 아닌 &lt;code&gt;r - l &gt; 1&lt;/code&gt; 로 쓰면 나머지 구간 탐색이 부족해 최대점을 놓칠 수 있습니다.&lt;/p&gt;
&lt;h3 id=&quot;4-이분-탐색과의-혼동&quot;&gt;4. 이분 탐색과의 혼동&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
삼분 탐색은 단봉 함수에만 유효. [[binary-search|이분 탐색]] 은 단조 함수 전용. 조건 혼용 금지.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;5-반복-횟수-과소&quot;&gt;5. 반복 횟수 과소&lt;/h3&gt;
&lt;p&gt;실수 탐색에서 iter=50 이면 $(2/3)^{50} \approx 1.2 \times 10^{-9}$ 의 정밀도. 보통 문제에서는 $10^{-9}$ 이상 요구하므로 iter=100~200 권장.&lt;/p&gt;
&lt;h3 id=&quot;6-황금-분할-초기-f-2회-호출-후-1회&quot;&gt;6. 황금 분할 초기 f 2회 호출 후 1회&lt;/h3&gt;
&lt;p&gt;황금 분할도 &lt;strong&gt;첫 반복에 f 2회&lt;/strong&gt;, 이후부터 1회. 첫 m1, m2 계산은 별도 처리 필요.&lt;/p&gt;
&lt;h3 id=&quot;7-볼록-다각형-극값-탐색&quot;&gt;7. 볼록 다각형 극값 탐색&lt;/h3&gt;
&lt;p&gt;볼록 다각형에서 특정 방향 최대 꼭짓점 탐색에 삼분 탐색 적용 가능. 단 마지막 꼭짓점과 첫 꼭짓점의 연결이 단봉을 만족하는지 확인.&lt;/p&gt;
&lt;h2 id=&quot;변형-최솟값-탐색&quot;&gt;변형: 최솟값 탐색&lt;/h2&gt;
&lt;p&gt;함수 부호를 반전하거나 조건을 반대로:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ternary_min&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(l, r, iters&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;200&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; _ &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(iters):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        m1 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; l &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (r &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; l) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        m2 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (r &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; l) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; f(m1) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; f(m2): l &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; m1  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 최솟값 조건 반전&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        else&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: r &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; m2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (l &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;boj-연습-문제&quot;&gt;BOJ 연습 문제&lt;/h2&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;번호&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;제목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;유형&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 8983&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;사냥꾼&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;삼분 탐색 기본&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 11664&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;선분과 점&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;실수 삼분 탐색&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1equa&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;방정식 근사&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;이분 + 삼분 혼합&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 13306&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;트리&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;파라메트릭 탐색&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 10989&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;수 정렬하기 3&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;정수 삼분 탐색 응용&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[binary-search|이분 탐색]] (단조 함수용)&lt;/li&gt;
&lt;li&gt;[[ternary-search|Ternary Search]] (삼분 탐색 상세)&lt;/li&gt;
&lt;li&gt;[[parametric-search|Parametric Search]] (결정 함수 이분)&lt;/li&gt;
&lt;li&gt;[[cht|Convex Hull Trick]] (볼록 함수 특수 최적화)&lt;/li&gt;
&lt;li&gt;[[calculus|미적분]] (연속 함수 극값 수학적 접근)&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>algorithm</category><category>math</category><category>search</category><category>optimization</category><category>unimodal</category><author>koa (김신건)</author></item><item><title>Graph DP: 트리/DAG 위 DP</title><link>https://shinkeonkim.com/wiki/algorithm/dp/graph-dp/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/algorithm/dp/graph-dp/</guid><pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Graph DP&lt;/strong&gt; 는 그래프 구조를 이용한 DP. 사이클 없는 그래프 (트리, DAG) 에서 특히 자연스럽다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tree DP&lt;/strong&gt;: 루트 트리에서 자식 서브트리 결과를 조합&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DAG DP&lt;/strong&gt;: 위상 정렬 후 순서대로 전이 계산&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rerooting&lt;/strong&gt;: Tree DP 를 모든 정점이 루트일 때의 답으로 확장&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;문제-상황&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p&gt;배열 DP (1D, 2D) 는 인덱스 순서로 전이. 그래프에서는 인덱스 대신 &lt;strong&gt;노드 관계&lt;/strong&gt; 가 DP 전이를 결정.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;트리 문제 예시&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;서브트리 크기, 트리 지름, 최대 독립 집합&lt;/li&gt;
&lt;li&gt;각 노드를 루트로 했을 때의 트리 전체 답&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;DAG 문제 예시&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;가장 긴 경로 (Longest Path in DAG)&lt;/li&gt;
&lt;li&gt;경로 수 세기, 최소/최대 비용 경로&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;시각화&quot;&gt;시각화&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    root((&quot;1\n루트&quot;)) --&gt; A((&quot;2&quot;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    root --&gt; B((&quot;3&quot;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A --&gt; C((&quot;4&quot;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A --&gt; D((&quot;5&quot;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B --&gt; E((&quot;6&quot;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B --&gt; F((&quot;7&quot;))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;DFS 순서: 1 -&gt; 2 -&gt; 4, 5 -&gt; 3 -&gt; 6, 7.&lt;/p&gt;
&lt;p&gt;리프 노드부터 계산해 부모에게 전달.&lt;/p&gt;
&lt;h2 id=&quot;핵심-아이디어&quot;&gt;핵심 아이디어&lt;/h2&gt;
&lt;h3 id=&quot;tree-dp-기본-구조&quot;&gt;Tree DP: 기본 구조&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;dfs(u, parent):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    dp[u] = base_value(u)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for v in adj[u]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        if v == parent: continue  // 부모 방향 스킵&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        dfs(v, u)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        dp[u] = combine(dp[u], dp[v])&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;combine&lt;/code&gt; 은 문제마다 다름:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;서브트리 크기: &lt;code&gt;dp[u] += dp[v]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;서브트리 최대 독립 집합: 더 복잡 (선택/불선택 두 상태)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;rerooting-두-번의-dfs&quot;&gt;Rerooting: 두 번의 DFS&lt;/h3&gt;
&lt;p&gt;특정 루트 기준 Tree DP 는 O(N). 모든 정점을 루트로 하는 답이 필요하면 &lt;strong&gt;rerooting&lt;/strong&gt; 으로 O(N) 에 해결.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    DFS1[&quot;DFS 1: 임의 루트로\ndown DP 계산&quot;] --&gt; DFS2[&quot;DFS 2: up DP 계산\nrerooting 전이&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    DFS2 --&gt; Result[&quot;모든 정점 루트로 했을 때의 답&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;dag-dp&quot;&gt;DAG DP&lt;/h3&gt;
&lt;p&gt;[[topological-sorting|위상 정렬]] 후, in-degree 0 부터 순서대로 계산.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;topo_sort(graph) -&gt; order&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for u in order:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for v in adj[u]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        dp[v] = update(dp[v], dp[u])&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;알고리즘&quot;&gt;알고리즘&lt;/h2&gt;
&lt;h3 id=&quot;tree-dp-서브트리-크기&quot;&gt;Tree DP: 서브트리 크기&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sz[MAXN];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; dfs_sz&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; u&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; p&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    sz[u] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; v : adj[u]) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (v &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; p) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;        dfs_sz&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(v, u);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        sz[u] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sz[v];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;tree-dp-최대-독립-집합&quot;&gt;Tree DP: 최대 독립 집합&lt;/h3&gt;
&lt;p&gt;각 정점에서 “선택” / “불선택” 두 상태:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;dp[u][1]&lt;/code&gt; = u 를 선택했을 때 최대 크기&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dp[u][0]&lt;/code&gt; = u 를 선택 안 했을 때 최대 크기&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dp[MAXN][&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; dfs_mis&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; u&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; p&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    dp[u][&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   // 자신 선택&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    dp[u][&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   // 자신 불선택&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; v : adj[u]) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (v &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; p) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;        dfs_mis&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(v, u);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        dp[u][&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dp[v][&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;];&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;         // 자신 선택하면 자식 불선택&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        dp[u][&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; max&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(dp[v][&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;], dp[v][&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]);&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; // 자신 불선택하면 자식 중 최적&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 답: max(dp[root][0], dp[root][1])&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;rerooting-트리의-모든-정점에서의-거리-합&quot;&gt;Rerooting: 트리의 모든 정점에서의 거리 합&lt;/h3&gt;
&lt;p&gt;전형적인 rerooting 예제.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;long&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; long&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dist_sum[MAXN];&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // u 를 루트로 했을 때 모든 노드까지 거리 합&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;long&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; long&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sub_sum[MAXN];&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   // u 서브트리 노드까지 거리 합 (루트 기준)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;long&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; long&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sub_cnt[MAXN];&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   // u 서브트리 크기&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// DFS 1: 루트 r=0 기준 down DP&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; dfs1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; u&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; p&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    sub_cnt[u] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    sub_sum[u] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; v : adj[u]) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (v &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; p) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;        dfs1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(v, u);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        sub_cnt[u] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sub_cnt[v];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        sub_sum[u] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sub_sum[v] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sub_cnt[v];&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // v 서브트리의 모든 노드가 +1 거리&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// DFS 2: rerooting (위로 정보 전달)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; dfs2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; u&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; p&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; N&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; v : adj[u]) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (v &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; p) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        // u 에서 v 로 루트 변경 시:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        // v 서브트리 외 노드 수 = N - sub_cnt[v]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        // 그 노드들의 v 에서의 거리 = dist_sum[u] - (sub_sum[v] + sub_cnt[v]) + (N - sub_cnt[v])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        dist_sum[v] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dist_sum[u]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                    -&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (sub_sum[v] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sub_cnt[v])&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   // 제거: v 방향 기여&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                    +&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (N &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sub_cnt[v]);&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;            // 추가: 나머지 노드들 +1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;        dfs2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(v, u, N);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;dag-dp-최장-경로&quot;&gt;DAG DP: 최장 경로&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;vector&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;int&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; order;&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // 위상 정렬 순서&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;long&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; long&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dp[MAXN];&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; // dp[u] = u 에서 시작하는 최장 경로&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 위상 정렬 후&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; u : order) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;auto&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [v, w] : adj[u]) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        dp[u] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; max&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(dp[u], dp[v] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; w);&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // v 로 가는 에지&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 답: max(dp[u]) for all u&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;구현&quot;&gt;구현&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
variants={[
{
language: “cpp”,
label: “C++: Tree DP, 서브트리 크기”,
code: `#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;&lt;/p&gt;
&lt;p&gt;const int MAXN = 100005;
vector&lt;int&gt; adj[MAXN];
int sz[MAXN], dp0[MAXN], dp1[MAXN];&lt;/int&gt;&lt;/p&gt;
&lt;p&gt;void dfs(int u, int p) {
sz[u] = 1;
dp0[u] = 0; dp1[u] = 1;
for (int v : adj[u]) {
if (v == p) continue;
dfs(v, u);
sz[u] += sz[v];
dp1[u] += dp0[v];
dp0[u] += max(dp0[v], dp1[v]);
}
}&lt;/p&gt;
&lt;p&gt;int main() {
int n; cin &gt;&gt; n;
for (int i = 0; i &amp;#x3C; n - 1; i++) {
int u, v; cin &gt;&gt; u &gt;&gt; v;
adj[u].push_back(v);
adj[v].push_back(u);
}
dfs(1, 0);
int root = 1;
cout &amp;#x3C;&amp;#x3C; “서브트리 크기: ” &amp;#x3C;&amp;#x3C; sz[root] &amp;#x3C;&amp;#x3C; “\n”;
cout &amp;#x3C;&amp;#x3C; “최대 독립 집합: ” &amp;#x3C;&amp;#x3C; max(dp0[root], dp1[root]) &amp;#x3C;&amp;#x3C; “\n”;
return 0;
}&lt;code&gt;,     },     {       language: &quot;python&quot;,       label: &quot;Python: Tree DP&quot;,       code: &lt;/code&gt;import sys
sys.setrecursionlimit(200000)&lt;/p&gt;
&lt;p&gt;def solve():
n = int(input())
adj = [[] for _ in range(n + 1)]
for _ in range(n - 1):
u, v = map(int, input().split())
adj[u].append(v)
adj[v].append(u)&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sz = [1] * (n + 1)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;dp0 = [0] * (n + 1)  # 자신 불선택&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;dp1 = [1] * (n + 1)  # 자신 선택&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;def dfs(u, p):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for v in adj[u]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        if v == p:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            continue&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        dfs(v, u)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        sz[u] += sz[v]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        dp1[u] += dp0[v]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        dp0[u] += max(dp0[v], dp1[v])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;dfs(1, 0)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;print(&quot;서브트리 크기:&quot;, sz[1])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;print(&quot;최대 독립 집합:&quot;, max(dp0[1], dp1[1]))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;solve()&lt;code&gt;,     },   ]}   cases={[     {       label: &quot;기본 (n=7, path graph)&quot;,       input: &lt;/code&gt;7
1 2
1 3
2 4
2 5
3 6
3 7&lt;code&gt;,       output: &lt;/code&gt;서브트리 크기: 7
최대 독립 집합: 4`,
},
]}
/&gt;&lt;/p&gt;
&lt;h2 id=&quot;복잡도&quot;&gt;복잡도&lt;/h2&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;알고리즘&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;시간&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;공간&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;비고&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Tree DP (기본)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;$O(N)$&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;$O(N)$&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;각 노드 1번 방문&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Tree DP (2상태, k상태)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;$O(N \cdot k)$&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;$O(N \cdot k)$&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;상태 수 k&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Rerooting&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;$O(N)$&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;$O(N)$&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;DFS 2번&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;DAG DP&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;$O(V + E)$&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;$O(V)$&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;위상 정렬 + DP&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Tree DP 에서 자식 결합이 O(1) 이면 전체 O(N).
서브트리 결합이 O(k) 이면 전체 O(N * k).&lt;/p&gt;
&lt;h2 id=&quot;함정&quot;&gt;함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
&lt;strong&gt;재귀 깊이&lt;/strong&gt;: n = 10^5 인 bamboo tree (경로 그래프) 에서 재귀 DFS 는 스택 오버플로우. 반복 DFS 또는 재귀 제한 증가 필요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
&lt;strong&gt;Rerooting 전이 오류&lt;/strong&gt;: &lt;code&gt;dist_sum[v]&lt;/code&gt; 를 계산할 때 v 서브트리의 기여를 빼고 나머지를 더하는 전이식 방향을 헷갈리기 쉬움. 작은 예시로 손 검증 필수.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
&lt;strong&gt;간선 방향&lt;/strong&gt;: DAG DP 에서 에지 방향이 반대이면 위상 정렬 순서와 전이 방향도 반대. 문제 조건 꼼꼼히 확인.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;흔한-실수&quot;&gt;흔한 실수&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;부모 노드 체크 없이 DFS: 무한 루프 (&lt;code&gt;if v != parent continue&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;리프 노드의 &lt;code&gt;dp&lt;/code&gt; 초기화를 잊음&lt;/li&gt;
&lt;li&gt;Rerooting 에서 두 번째 DFS 에 &lt;code&gt;dist_sum[root]&lt;/code&gt; 를 &lt;code&gt;sub_sum[root]&lt;/code&gt; 로 초기화 안 함&lt;/li&gt;
&lt;li&gt;DAG 의 위상 정렬 전 DP 전이: 방문 안 된 노드의 값 사용&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;boj-연습-문제&quot;&gt;BOJ 연습 문제&lt;/h2&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;번호&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;제목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;키워드&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1167&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;트리의 지름&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tree DP, DFS 2회&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 2533&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;사회망 서비스&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tree DP, 최대 독립 집합&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 2213&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;트리의 독립 집합&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tree DP, 2상태&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1149&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;RGB 거리&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;선형 DP (Tree DP 기초)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 14267&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;회사 문화 1&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tree DP + 위로 전달&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1516&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;게임 개발&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;DAG DP&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[topological-sorting|위상 정렬]]&lt;/li&gt;
&lt;li&gt;[[tree-diameter|Tree Diameter]]&lt;/li&gt;
&lt;li&gt;[[dp-bitfield|DP Bitfield]]&lt;/li&gt;
&lt;li&gt;[[dp|다이나믹 프로그래밍]]&lt;/li&gt;
&lt;li&gt;[[dp-tree|Tree DP 심화]]&lt;/li&gt;
&lt;li&gt;[[dfs|DFS]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>algorithm</category><category>dp</category><category>graph</category><category>tree</category><author>koa (김신건)</author></item><item><title>Hamiltonian Path: 모든 정점 한 번씩</title><link>https://shinkeonkim.com/wiki/algorithm/graph/hamiltonian-path/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/algorithm/graph/hamiltonian-path/</guid><pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Hamiltonian Path&lt;/strong&gt; 는 그래프의 &lt;strong&gt;모든 정점을 정확히 한 번씩&lt;/strong&gt; 방문하는 경로. 시작 = 끝이면 &lt;strong&gt;Hamiltonian Cycle&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;일반 그래프에서 &lt;strong&gt;NP-complete&lt;/strong&gt;. Euler path (모든 간선을 한 번씩) 와 대비: Euler 는 다항 시간(Hierholzer 알고리즘).&lt;/p&gt;
&lt;h2 id=&quot;문제-상황&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p&gt;N개의 정점과 M개의 간선으로 이루어진 그래프에서 모든 정점을 정확히 한 번씩 방문하는 경로가 존재하는가?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;naive (백트래킹)&lt;/strong&gt;: 모든 정점 순열을 시도. O(N! x N). N=15 이상이면 불가능.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;핵심 통찰&lt;/strong&gt;: “방문한 정점 집합” 자체를 상태로 만들어 DP. &lt;code&gt;dp[mask][v]&lt;/code&gt;를 “방문 집합 = mask, 현재 v에 있을 때 도달 가능 여부”로 정의. 상태 수 O(2^N x N), 전이 O(N).&lt;/p&gt;
&lt;h2 id=&quot;시각화&quot;&gt;시각화&lt;/h2&gt;
&lt;h3 id=&quot;bitmask-dp-상태-전이-n4-정점-0에서-출발&quot;&gt;Bitmask DP 상태 전이 (N=4, 정점 0에서 출발)&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S[&quot;출발: 정점 0만 방문&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A[&quot;정점 0,1 방문, 현재 위치 1&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B[&quot;정점 0,2 방문, 현재 위치 2&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C[&quot;정점 0,1,2 방문, 현재 위치 2&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    D[&quot;정점 0,1,3 방문, 현재 위치 3&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    E[&quot;정점 0,1,2,3 방문 완료&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S --&gt;|&quot;0에서 1 이동&quot;| A&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S --&gt;|&quot;0에서 2 이동&quot;| B&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A --&gt;|&quot;1에서 2 이동&quot;| C&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A --&gt;|&quot;1에서 3 이동&quot;| D&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C --&gt;|&quot;2에서 3 이동&quot;| E&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    D --&gt;|&quot;3에서 2 이동&quot;| E&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;bitmask-표현-n4&quot;&gt;Bitmask 표현 (N=4)&lt;/h3&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;mask (이진)&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;십진&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;방문 정점 집합&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;0001&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;{0}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;0011&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;3&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;{0, 1}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;0101&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;5&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;{0, 2}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;0111&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;7&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;{0, 1, 2}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;1111&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;15&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;{0, 1, 2, 3} (전체)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;핵심-아이디어&quot;&gt;핵심 아이디어&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;상태&lt;/strong&gt;: &lt;code&gt;dp[mask][v]&lt;/code&gt; = “방문 집합 = mask, 현재 v에 있는 상태가 도달 가능한가.”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;초기&lt;/strong&gt;: &lt;code&gt;dp[1 &amp;#x3C;&amp;#x3C; s][s] = true&lt;/code&gt; (출발 정점 s 하나만 방문).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;전이&lt;/strong&gt;: 현재 상태 (mask, u)에서 미방문 정점 v로 이동.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;if dp[mask][u] and edge(u,v) and v not in mask:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    dp[mask | (1 &amp;#x3C;&amp;#x3C; v)][v] = true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;답&lt;/strong&gt;: &lt;code&gt;full = (1 &amp;#x3C;&amp;#x3C; N) - 1&lt;/code&gt;인 전체 집합을 포함하는 mask에서 도달 가능한 v가 존재하면 YES.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hamiltonian &lt;strong&gt;Path&lt;/strong&gt;: &lt;code&gt;any(dp[full][v] for v in 0..N-1)&lt;/code&gt; → 경로만 확인&lt;/li&gt;
&lt;li&gt;Hamiltonian &lt;strong&gt;Cycle&lt;/strong&gt;: &lt;code&gt;any(dp[full][v] and edge(v, start) for v in 0..N-1)&lt;/code&gt; → 시작점 복귀&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;비트-연산-정리&quot;&gt;비트 연산 정리&lt;/h3&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;연산&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;코드&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;v 방문 여부 확인&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;mask &amp;#x26; (1 &amp;#x3C;&amp;#x3C; v)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;v 추가&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;`mask&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;v 제거&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;mask &amp;#x26; ~(1 &amp;#x3C;&amp;#x3C; v)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;전체 집합&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;(1 &amp;#x3C;&amp;#x3C; N) - 1&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;알고리즘&quot;&gt;알고리즘&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Hamiltonian Path Bitmask DP&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# 단일 출발점 s 기준&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;dp[1 &amp;#x3C;&amp;#x3C; s][s] = true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for mask = 1 to (1&amp;#x3C;&amp;#x3C;N) - 1:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for u = 0 to N-1:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        if not dp[mask][u]: continue&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        if not (mask &amp;#x26; (1 &amp;#x3C;&amp;#x3C; u)): continue   # 유효 상태 체크&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        for v in adj[u]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            if mask &amp;#x26; (1 &amp;#x3C;&amp;#x3C; v): continue      # 이미 방문&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            dp[mask | (1 &amp;#x3C;&amp;#x3C; v)][v] = true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;full = (1 &amp;#x3C;&amp;#x3C; N) - 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;answer = any(dp[full][v] for v in 0..N-1)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;구현&quot;&gt;구현&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
variants={[
{
language: “cpp”,
label: “C++”,
code: `#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;&lt;/p&gt;
&lt;p&gt;int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;int n, m;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cin &gt;&gt; n &gt;&gt; m;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;vector&amp;#x3C;vector&amp;#x3C;int&gt;&gt; adj(n);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for (int i = 0; i &amp;#x3C; m; i++) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    int u, v;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    cin &gt;&gt; u &gt;&gt; v;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    adj[u].push_back(v);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    adj[v].push_back(u);  // 무방향 그래프&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// dp[mask][v]: mask 집합 방문 후 v에 위치 가능한가&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;int FULL = (1 &amp;#x3C;&amp;#x3C; n) - 1;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;vector&amp;#x3C;vector&amp;#x3C;bool&gt;&gt; dp(1 &amp;#x3C;&amp;#x3C; n, vector&amp;#x3C;bool&gt;(n, false));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 모든 정점을 출발점으로 시도&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for (int s = 0; s &amp;#x3C; n; s++)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    dp[1 &amp;#x3C;&amp;#x3C; s][s] = true;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for (int mask = 1; mask &amp;#x3C;= FULL; mask++) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for (int u = 0; u &amp;#x3C; n; u++) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        if (!dp[mask][u]) continue;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        if (!(mask &amp;#x26; (1 &amp;#x3C;&amp;#x3C; u))) continue;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        for (int v : adj[u]) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            if (mask &amp;#x26; (1 &amp;#x3C;&amp;#x3C; v)) continue;  // 이미 방문&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            dp[mask | (1 &amp;#x3C;&amp;#x3C; v)][v] = true;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;bool found = false;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for (int v = 0; v &amp;#x3C; n; v++)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if (dp[FULL][v]) { found = true; break; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cout &amp;#x3C;&amp;#x3C; (found ? &quot;YES&quot; : &quot;NO&quot;) &amp;#x3C;&amp;#x3C; &quot;\\n&quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;return 0;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;code&gt;,     },     {       language: &quot;python&quot;,       label: &quot;Python&quot;,       code: &lt;/code&gt;import sys
input = sys.stdin.readline&lt;/p&gt;
&lt;p&gt;def solve():
n, m = map(int, input().split())
adj = [[] for _ in range(n)]
for _ in range(m):
u, v = map(int, input().split())
adj[u].append(v)
adj[v].append(u)&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;FULL = (1 &amp;#x3C;&amp;#x3C; n) - 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# dp[mask][v]: mask 집합 방문 후 v에 위치 가능한가&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;dp = [[False] * n for _ in range(1 &amp;#x3C;&amp;#x3C; n)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for s in range(n):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    dp[1 &amp;#x3C;&amp;#x3C; s][s] = True&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for mask in range(1, FULL + 1):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for u in range(n):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        if not dp[mask][u]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            continue&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        if not (mask &gt;&gt; u &amp;#x26; 1):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            continue&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        for v in adj[u]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            if mask &gt;&gt; v &amp;#x26; 1:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                continue  # 이미 방문&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            dp[mask | (1 &amp;#x3C;&amp;#x3C; v)][v] = True&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;found = any(dp[FULL][v] for v in range(n))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;print(&quot;YES&quot; if found else &quot;NO&quot;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;solve()&lt;code&gt;,     },   ]}   cases={[     {       label: &quot;경로 존재&quot;,       input: &lt;/code&gt;4 5
0 1
1 2
2 3
0 2
1 3&lt;code&gt;,       output: &lt;/code&gt;YES&lt;code&gt;,     },     {       label: &quot;경로 없음&quot;,       input: &lt;/code&gt;4 2
0 1
2 3&lt;code&gt;,       output: &lt;/code&gt;NO`,
},
]}
/&gt;&lt;/p&gt;
&lt;h2 id=&quot;복잡도&quot;&gt;복잡도&lt;/h2&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;항목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;값&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;시간&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(2^N x N^2)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;공간&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(2^N x N)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;N=15&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;2^15 x 225 ~ 7.4M (여유)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;N=20&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;2^20 x 400 ~ 420M (타이트)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;실용 한계&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;N &amp;#x3C;= 20&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
N=20 기준 메모리: &lt;code&gt;bool dp[2^20][20]&lt;/code&gt; = 약 20MB. &lt;code&gt;int&lt;/code&gt; 사용 시 80MB.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;함정&quot;&gt;함정&lt;/h2&gt;
&lt;h3 id=&quot;1-유효-상태-체크-누락&quot;&gt;1. 유효 상태 체크 누락&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(mask &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x3C;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; u))) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;continue&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 이 체크가 없으면 u가 mask에 없는 잘못된 상태에서 전이 발생&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2-출발-정점-범위&quot;&gt;2. 출발 정점 범위&lt;/h3&gt;
&lt;p&gt;Hamiltonian Path는 어떤 정점에서도 시작 가능. 모든 s에 대해 &lt;code&gt;dp[1&amp;#x3C;&amp;#x3C;s][s]=true&lt;/code&gt;로 초기화. 시작점을 0으로 고정하면 방향 있는 경우에만 유효.&lt;/p&gt;
&lt;h3 id=&quot;3-방향-vs-무방향&quot;&gt;3. 방향 vs 무방향&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;무방향: &lt;code&gt;adj[u].push_back(v)&lt;/code&gt; 와 &lt;code&gt;adj[v].push_back(u)&lt;/code&gt; 둘 다&lt;/li&gt;
&lt;li&gt;방향: 단방향만. Hamiltonian Cycle 존재 조건이 달라짐.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;4-cycle-vs-path-혼동&quot;&gt;4. Cycle vs Path 혼동&lt;/h3&gt;
&lt;p&gt;Cycle: 마지막 정점 v에서 출발점 s로 돌아올 수 있는 간선 존재 확인 필요.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Hamiltonian Cycle 확인 (출발 s 고정 시)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; v &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; v &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n; v&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (dp[FULL][v] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; adj_matrix[v][s]) found &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;5-백트래킹-vs-dp&quot;&gt;5. 백트래킹 vs DP&lt;/h3&gt;
&lt;p&gt;N &amp;#x3C;= 12 정도는 백트래킹도 빠름. N=15~20에서 DP가 확실히 유리. N &gt; 20은 모두 불가.&lt;/p&gt;
&lt;h2 id=&quot;boj-연습-문제&quot;&gt;BOJ 연습 문제&lt;/h2&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;번호&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;제목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;난이도&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;알고리즘&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1194&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;달이 차오른다, 가자.&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Gold 1&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;BFS + bitmask&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 2098&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;외판원 순회&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Gold 1&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;TSP, bitmask DP&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1987&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;알파벳&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Gold 4&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;백트래킹&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 17471&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;게리맨더링&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Gold 4&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;부분집합 + bitmask&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[tsp|TSP]] (Hamiltonian Cycle + 최소 비용)&lt;/li&gt;
&lt;li&gt;[[dp-bitfield|Bitmask DP]] (비트 연산 상세)&lt;/li&gt;
&lt;li&gt;[[dp-on-bitmask|DP on Bitmask]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>algorithm</category><category>graph</category><category>hamiltonian</category><category>np-hard</category><author>koa (김신건)</author></item><item><title>Hash Table: 해시 기반 딕셔너리</title><link>https://shinkeonkim.com/wiki/algorithm/data-structure/hash-table/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/algorithm/data-structure/hash-table/</guid><pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Hash Table&lt;/strong&gt; 은 해시 함수로 키를 배열 인덱스에 매핑하여 &lt;strong&gt;평균 O(1)&lt;/strong&gt; 삽입/탐색/삭제를 제공하는 자료구조.&lt;/p&gt;
&lt;p&gt;키 k를 인덱스 &lt;code&gt;h(k) mod m&lt;/code&gt;으로 변환하여 버킷 배열에 저장. 두 키가 같은 인덱스를 가리키면 &lt;strong&gt;충돌(collision)&lt;/strong&gt; 발생.&lt;/p&gt;
&lt;h2 id=&quot;문제-상황&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p&gt;N개의 키-값 쌍을 저장하고, 임의 키에 대해 빠르게 값을 조회/삽입/삭제해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;naive (정렬 배열)&lt;/strong&gt;: 이진 탐색으로 O(log N) 조회. 삽입/삭제 O(N).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;naive (연결 리스트)&lt;/strong&gt;: 탐색 O(N). 삽입 O(1).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;핵심 통찰&lt;/strong&gt;: 해시 함수로 키를 인덱스로 변환. 충돌이 없다면 O(1) 모든 연산. 충돌을 체이닝 또는 개방 주소법으로 처리.&lt;/p&gt;
&lt;h2 id=&quot;시각화&quot;&gt;시각화&lt;/h2&gt;
&lt;h3 id=&quot;chaining-구조-버킷-배열--연결-리스트&quot;&gt;Chaining 구조 (버킷 배열 + 연결 리스트)&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph &quot;버킷 배열&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        B0[&quot;버킷 0&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        B1[&quot;버킷 1&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        B2[&quot;버킷 2&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        B3[&quot;버킷 3&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C0[&quot;apple: 사과&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C2a[&quot;banana: 바나나&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C2b[&quot;cherry: 체리&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C3[&quot;date: 대추&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B0 --&gt; C0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B1 --&gt;|&quot;empty&quot;| X1[&quot; &quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B2 --&gt; C2a&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C2a --&gt;|&quot;충돌, 체이닝&quot;| C2b&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B3 --&gt; C3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;open-addressing-선형-탐사&quot;&gt;Open Addressing (선형 탐사)&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    In[&quot;insert(key, val)\nh = hash(key) mod m&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A{&quot;배열[h] 비었나?&quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B[&quot;배열[h]에 저장&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C[&quot;h = (h+1) mod m\n다음 슬롯 탐색&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    In --&gt; A&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A --&gt;|&quot;YES&quot;| B&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A --&gt;|&quot;NO, 충돌&quot;| C&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C --&gt; A&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;핵심-아이디어&quot;&gt;핵심 아이디어&lt;/h2&gt;
&lt;h3 id=&quot;chaining&quot;&gt;Chaining&lt;/h3&gt;
&lt;p&gt;각 버킷에 연결 리스트/트리 저장. 충돌 시 리스트에 추가.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;삽입&lt;/strong&gt;: O(1) amortized (리스트 헤드에 추가)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;탐색&lt;/strong&gt;: O(1) 평균, O(N) 최악 (해시 충돌 집중 시)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;부하율(load factor)&lt;/strong&gt; alpha = N/M: alpha가 높으면 평균 체인 길이 증가&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;open-addressing&quot;&gt;Open Addressing&lt;/h3&gt;
&lt;p&gt;충돌 시 배열 내 다른 슬롯 탐색. 별도 메모리 필요 없음.&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;탐사 방법&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;다음 슬롯&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;특징&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Linear probing&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;(h + i) mod m&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;클러스터링 문제&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Quadratic probing&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;(h + i^2) mod m&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;일차 클러스터링 완화&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Double hashing&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;(h1 + i*h2) mod m&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;최적. h2 신중히 선택&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;부하율 0.5~0.7 유지&lt;/strong&gt; 권장. 초과 시 rehash(테이블 크기 2배 확장).&lt;/p&gt;
&lt;h3 id=&quot;해시-함수-선택&quot;&gt;해시 함수 선택&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;정수 키: &lt;code&gt;h(k) = k mod m&lt;/code&gt; (m은 소수 추천)&lt;/li&gt;
&lt;li&gt;문자열: Polynomial rolling hash &lt;code&gt;h = (c0 + c1*p + c2*p^2 + ...) mod m&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;적대적 입력 대비: SipHash (C++ unordered_map의 일부 구현), 무작위 salt&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;알고리즘&quot;&gt;알고리즘&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Chaining 삽입&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;insert(table, key, val):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    h = hash(key) mod m&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    table[h].append((key, val))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Chaining 탐색&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;search(table, key):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    h = hash(key) mod m&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for (k, v) in table[h]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        if k == key: return v&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return NOT_FOUND&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Rehash (부하율 초과 시)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;rehash(table):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    new_table = new array of size 2*m&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for each entry (key, val) in table:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        new_h = hash(key) mod (2*m)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        new_table[new_h].append((key, val))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return new_table&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;구현&quot;&gt;구현&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
variants={[
{
language: “cpp”,
label: “C++ (unordered_map)”,
code: `#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;&lt;/p&gt;
&lt;p&gt;int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;int n, q;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cin &gt;&gt; n &gt;&gt; q;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;unordered_map&amp;#x3C;string, int&gt; table;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;table.reserve(1 &amp;#x3C;&amp;#x3C; 17);  // 초기 버킷 크기 예약 (rehash 방지)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 삽입&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for (int i = 0; i &amp;#x3C; n; i++) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    string key;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    int val;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    cin &gt;&gt; key &gt;&gt; val;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    table[key] = val;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 쿼리&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for (int i = 0; i &amp;#x3C; q; i++) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    string key;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    cin &gt;&gt; key;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    auto it = table.find(key);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if (it != table.end()) cout &amp;#x3C;&amp;#x3C; it-&gt;second &amp;#x3C;&amp;#x3C; &quot;\\n&quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    else cout &amp;#x3C;&amp;#x3C; &quot;NOT_FOUND\\n&quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;return 0;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;code&gt;,     },     {       language: &quot;python&quot;,       label: &quot;Python (dict)&quot;,       code: &lt;/code&gt;import sys
input = sys.stdin.readline&lt;/p&gt;
&lt;p&gt;def solve():
n, q = map(int, input().split())&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;table = {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for _ in range(n):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    parts = input().split()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    key, val = parts[0], int(parts[1])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    table[key] = val&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for _ in range(q):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    key = input().strip()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if key in table:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        print(table[key])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    else:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        print(&quot;NOT_FOUND&quot;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;solve()&lt;code&gt;,     },   ]}   cases={[     {       label: &quot;기본 삽입/조회&quot;,       input: &lt;/code&gt;3 3
apple 1
banana 2
cherry 3
banana
grape
apple&lt;code&gt;,       output: &lt;/code&gt;2
NOT_FOUND
1&lt;code&gt;,     },     {       label: &quot;중복 키 갱신&quot;,       input: &lt;/code&gt;3 1
x 10
x 20
x 30
x&lt;code&gt;,       output: &lt;/code&gt;30`,
},
]}
/&gt;&lt;/p&gt;
&lt;h2 id=&quot;복잡도&quot;&gt;복잡도&lt;/h2&gt;









































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;연산&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;평균&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;최악&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;조건&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;삽입&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(1)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;충돌 집중 시 최악&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;탐색&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(1)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;적대적 해시 입력&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;삭제&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(1)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Rehash&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;부하율 초과 시 발생&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;공간&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
트리 기반 맵(BST, &lt;code&gt;std::map&lt;/code&gt;)은 O(log N) 보장. 정렬 순서가 필요하면 BST, 순수 속도만 필요하면 Hash Table.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;언어별-구현&quot;&gt;언어별 구현&lt;/h2&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;언어&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;자료형&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;내부 구현&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;C++&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;unordered_map&lt;/code&gt;, &lt;code&gt;unordered_set&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;chaining (GCC)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Java&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;HashMap&lt;/code&gt;, &lt;code&gt;HashSet&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;chaining + tree (Java 8+, bucket &gt;= 8)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Python&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;dict&lt;/code&gt;, &lt;code&gt;set&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;open addressing (compact, 삽입 순서 보장 3.7+)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Go&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;map&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;chaining&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Rust&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;HashMap&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;SipHash 기본, open addressing&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;함정&quot;&gt;함정&lt;/h2&gt;
&lt;h3 id=&quot;1-최악-on-적대적-입력&quot;&gt;1. 최악 O(N): 적대적 입력&lt;/h3&gt;
&lt;p&gt;같은 해시값을 갖는 키만 삽입하면 체인 길이 O(N). C++ &lt;code&gt;unordered_map&lt;/code&gt;의 기본 해시는 예측 가능하여 취약.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;해결책&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 무작위 salt 기반 커스텀 해시&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; custom_hash&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    static&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; uint64_t&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; splitmix64&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;uint64_t&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; x&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        x &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; 0x&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;9e3779b97f4a7c15&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        x &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (x &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (x &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 30&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; 0x&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;bf58476d1ce4e5b9&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        x &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (x &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (x &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 27&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; 0x&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;94d049bb133111eb&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; x &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (x &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 31&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    size_t&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; operator&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;uint64_t&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; x&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        static&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; const&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; uint64_t&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; FIXED_RANDOM &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; chrono&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;steady_clock&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;time_since_epoch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;count&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; splitmix64&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(x &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; FIXED_RANDOM);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;unordered_map&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;long&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; long&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, custom_hash&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; safe_map;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2-부하율-초과-방치&quot;&gt;2. 부하율 초과 방치&lt;/h3&gt;
&lt;p&gt;부하율이 높아지면 충돌 증가로 성능 저하. &lt;code&gt;reserve&lt;/code&gt;로 미리 크기 확보하거나 &lt;code&gt;max_load_factor&lt;/code&gt; 조정.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;table.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;reserve&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x3C;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 17&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;table.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;max_load_factor&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0.25&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;3-iteration-순서&quot;&gt;3. iteration 순서&lt;/h3&gt;
&lt;p&gt;Python 3.7+는 삽입 순서 보장, C++ unordered_map은 임의 순서. 순서 의존 코드 주의.&lt;/p&gt;
&lt;h3 id=&quot;4-open-addressing-삭제&quot;&gt;4. Open Addressing 삭제&lt;/h3&gt;
&lt;p&gt;삭제된 슬롯을 그냥 비우면 이후 탐색 시 체인이 끊김. &lt;strong&gt;Tombstone&lt;/strong&gt; 마커로 표시하거나 backward shift deletion.&lt;/p&gt;
&lt;h3 id=&quot;5-정수-키에서--연산-오버플로&quot;&gt;5. 정수 키에서 % 연산 오버플로&lt;/h3&gt;
&lt;p&gt;C++에서 &lt;code&gt;long long&lt;/code&gt; 키를 &lt;code&gt;int&lt;/code&gt; 버킷 크기로 나누면 문제 없지만, 음수 키는 &lt;code&gt;%&lt;/code&gt; 연산이 음수 반환. &lt;code&gt;(h % m + m) % m&lt;/code&gt;으로 처리.&lt;/p&gt;
&lt;h2 id=&quot;boj-연습-문제&quot;&gt;BOJ 연습 문제&lt;/h2&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;번호&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;제목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;난이도&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;알고리즘&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1920&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;수 찾기&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Silver 4&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;해시셋 / 이분탐색&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 10546&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;배부른 마라토너&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Bronze 2&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;해시맵&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1764&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;듣보잡&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Silver 4&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;해시셋 교집합&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1717&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;집합의 표현&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Gold 5&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Disjoint Set (해시맵 응용 가능)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[binary-search-tree|BST]] (정렬 필요 시 대체, O(log N) 보장)&lt;/li&gt;
&lt;li&gt;[[bloom-filter|Bloom Filter]] (근사 존재 확인, 메모리 절약)&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>algorithm</category><category>data-structure</category><category>hash</category><category>map</category><author>koa (김신건)</author></item><item><title>Huffman Coding: 최적 접두 부호</title><link>https://shinkeonkim.com/wiki/algorithm/foundation/huffman-coding/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/algorithm/foundation/huffman-coding/</guid><pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Huffman Coding&lt;/strong&gt; 은 문자의 빈도가 주어졌을 때 &lt;strong&gt;평균 부호 길이를 최소화&lt;/strong&gt; 하는 &lt;strong&gt;접두 부호 (prefix code)&lt;/strong&gt; 를 만드는 그리디 알고리즘. David Huffman (1952) 이 MIT 학생 시절 발표.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;접두 부호&lt;/strong&gt;: 어떤 부호도 다른 부호의 접두어가 아님. 구분자 없이 디코딩 가능.&lt;/p&gt;
&lt;h2 id=&quot;문제-상황&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p&gt;N 개의 심볼과 각 빈도(혹은 확률)가 주어졌을 때, 각 심볼을 이진 부호로 표현해 전체 메시지의 &lt;strong&gt;평균 비트 수를 최소화&lt;/strong&gt; 하라.&lt;/p&gt;
&lt;p&gt;예: &lt;code&gt;a=5, b=9, c=12, d=13, e=16, f=45&lt;/code&gt; (총 100회 등장)&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;방식&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;설명&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;평균 비트/심볼&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;고정 길이&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;6개 심볼 -&gt; 최소 3비트 필요&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;3.00 bit&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Huffman&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;빈도에 따라 가변 길이&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;2.24 bit&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Shannon entropy&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;이론적 하한&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;~2.20 bit&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;고정 길이보다 약 25% 절약. 핵심 통찰: &lt;em&gt;빈도 높은 심볼에 짧은 부호, 드문 심볼에 긴 부호.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;핵심-아이디어&quot;&gt;핵심 아이디어&lt;/h2&gt;
&lt;p&gt;가장 빈도가 낮은 두 심볼을 &lt;strong&gt;반복적으로 합쳐&lt;/strong&gt; 이진 트리를 만든다. 트리 루트에서 각 리프까지의 경로 (왼쪽=0, 오른쪽=1) 가 최적 부호가 된다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A[&quot;빈도 계산&quot;] --&gt; B[&quot;min-heap 초기화&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B --&gt; C[&quot;최솟값 두 노드 pop&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C --&gt; D[&quot;합계로 내부 노드 생성&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    D --&gt; E[&quot;heap 에 push&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    E --&gt; F{&quot;heap 크기 = 1?&quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    F --&gt;|&quot;No&quot;| C&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    F --&gt;|&quot;Yes&quot;| G[&quot;루트 반환&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    G --&gt; H[&quot;DFS 로 부호 할당&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;알고리즘&quot;&gt;알고리즘&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;각 문자를 리프 노드로 하고 빈도를 우선순위로 하는 &lt;strong&gt;min-heap&lt;/strong&gt; 준비.&lt;/li&gt;
&lt;li&gt;Heap 에서 최소 두 노드를 pop, 두 빈도의 합을 값으로 하는 새 내부 노드 생성 (두 노드를 자식으로).&lt;/li&gt;
&lt;li&gt;새 노드를 heap 에 push.&lt;/li&gt;
&lt;li&gt;Heap 크기가 1 이 될 때까지 반복.&lt;/li&gt;
&lt;li&gt;완성된 트리의 루트에서 각 리프까지의 경로가 부호 (왼쪽=0, 오른쪽=1).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;O(N log N) - N 은 문자 종류 수.&lt;/p&gt;
&lt;h2 id=&quot;예시&quot;&gt;예시&lt;/h2&gt;
&lt;p&gt;빈도: &lt;code&gt;a=5, b=9, c=12, d=13, e=16, f=45&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;병합 과정:&lt;/p&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;단계&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;조작&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;heap 최솟값 순&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;초기&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;-&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;a:5, b:9, c:12, d:13, e:16, f:45&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;a(5)+b(9) = 14&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;c:12, d:13, ab:14, e:16, f:45&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;2&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;c(12)+d(13) = 25&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ab:14, e:16, cd:25, f:45&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;3&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ab(14)+e(16) = 30&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;cd:25, abe:30, f:45&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;4&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;cd(25)+abe(30) = 55&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;f:45, inner:55&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;5&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;f(45)+inner(55) = 100&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;root:100&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;완성된 트리 (왼쪽=0, 오른쪽=1):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    R[&quot;root: 100&quot;] --&gt; F[&quot;f: 45&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    R --&gt; N55[&quot;55&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    N55 --&gt; N25[&quot;25&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    N55 --&gt; N30[&quot;30&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    N25 --&gt; C[&quot;c: 12&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    N25 --&gt; D[&quot;d: 13&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    N30 --&gt; N14[&quot;14&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    N30 --&gt; E[&quot;e: 16&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    N14 --&gt; A[&quot;a: 5&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    N14 --&gt; B[&quot;b: 9&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;결과 부호 (평균 2.24 bit/symbol):&lt;/p&gt;















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;center&quot;&gt;심볼&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;빈도&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;부호&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;비트 수&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;center&quot;&gt;f&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;45&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;0&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;center&quot;&gt;c&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;12&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;100&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;3&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;center&quot;&gt;d&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;13&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;101&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;3&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;center&quot;&gt;e&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;16&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;111&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;3&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;center&quot;&gt;b&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;9&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;1101&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;4&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;center&quot;&gt;a&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;5&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;1100&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;4&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;평균 비트 = (45&lt;em&gt;1 + 12&lt;/em&gt;3 + 13&lt;em&gt;3 + 16&lt;/em&gt;3 + 9&lt;em&gt;4 + 5&lt;/em&gt;4) / 100 = 224/100 = &lt;strong&gt;2.24 bit&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;구현&quot;&gt;구현&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
variants={[
{
language: “cpp”,
label: “C++”,
code: `#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;&lt;/p&gt;
&lt;p&gt;struct Node {
char ch; int freq;
Node &lt;em&gt;l = nullptr, &lt;em&gt;r = nullptr;
};
struct Cmp { bool operator()(Node&lt;/em&gt; a, Node&lt;/em&gt; b) { return a-&gt;freq &gt; b-&gt;freq; } };&lt;/p&gt;
&lt;p&gt;void encode(Node* nd, string code, map&amp;#x3C;char,string&gt;&amp;#x26; codes) {
if (!nd-&gt;l &amp;#x26;&amp;#x26; !nd-&gt;r) { codes[nd-&gt;ch] = code; return; }
encode(nd-&gt;l, code+“0”, codes);
encode(nd-&gt;r, code+“1”, codes);
}&lt;/p&gt;
&lt;p&gt;int main() {
map&amp;#x3C;char,int&gt; freq = {{‘a’,5},{‘b’,9},{‘c’,12},{‘d’,13},{‘e’,16},{‘f’,45}};
priority_queue&amp;#x3C;Node*, vector&amp;#x3C;Node*&gt;, Cmp&gt; pq;
for (auto [c,f] : freq) pq.push(new Node{c,f});
while (pq.size() &gt; 1) {
auto a = pq.top(); pq.pop();
auto b = pq.top(); pq.pop();
auto m = new Node{‘\0’, a-&gt;freq + b-&gt;freq};
m-&gt;l = a; m-&gt;r = b;
pq.push(m);
}
map&amp;#x3C;char,string&gt; codes;
encode(pq.top(), &quot;&quot;, codes);
int bits = 0, total = 0;
for (auto [c,f] : freq) {
cout &amp;#x3C;&amp;#x3C; c &amp;#x3C;&amp;#x3C; ”: ” &amp;#x3C;&amp;#x3C; codes[c] &amp;#x3C;&amp;#x3C; “\n”;
bits += f * (int)codes[c].size();
total += f;
}
cout &amp;#x3C;&amp;#x3C; “avg = ” &amp;#x3C;&amp;#x3C; fixed &amp;#x3C;&amp;#x3C; setprecision(4)
&amp;#x3C;&amp;#x3C; (double)bits/total &amp;#x3C;&amp;#x3C; ” bit/symbol\n”;
}&lt;code&gt;,     },     {       language: &quot;python&quot;,       label: &quot;Python&quot;,       code: &lt;/code&gt;import heapq&lt;/p&gt;
&lt;p&gt;class Node:
def &lt;strong&gt;init&lt;/strong&gt;(self, ch, freq):
self.ch = ch; self.freq = freq
self.left = self.right = None
def &lt;strong&gt;lt&lt;/strong&gt;(self, other): return self.freq &amp;#x3C; other.freq&lt;/p&gt;
&lt;p&gt;def huffman(freq: dict) -&gt; dict:
heap = [Node(ch, f) for ch, f in freq.items()]
heapq.heapify(heap)
while len(heap) &gt; 1:
a = heapq.heappop(heap)
b = heapq.heappop(heap)
m = Node(None, a.freq + b.freq)
m.left, m.right = a, b
heapq.heappush(heap, m)
codes = {}
def dfs(node, code):
if node.ch is not None:
codes[node.ch] = code; return
dfs(node.left, code + “0”)
dfs(node.right, code + “1”)
dfs(heap[0], &quot;&quot;)
return codes&lt;/p&gt;
&lt;p&gt;freq = {“a”:5,“b”:9,“c”:12,“d”:13,“e”:16,“f”:45}
codes = huffman(freq)
total = sum(freq.values())
avg = sum(freq[c]*len(v) for c,v in codes.items()) / total
for ch, code in sorted(codes.items(), key=lambda x: (len(x[1]), x[0])):
print(f”{ch}: {code}”)
print(f”avg = {avg:.4f} bit/symbol”)&lt;code&gt;,     },   ]}   cases={[     {       label: &quot;기본 실행&quot;,       input: &quot;&quot;,       output: &lt;/code&gt;a: 1100
b: 1101
c: 100
d: 101
e: 111
f: 0
avg = 2.2400 bit/symbol`,
},
]}
/&gt;&lt;/p&gt;
&lt;h2 id=&quot;복잡도&quot;&gt;복잡도&lt;/h2&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;항목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;값&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;설명&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;트리 빌드&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(N log N)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;min-heap 연산 N-1 회&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;공간&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(N)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;트리 노드 수 = 2N-1&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;인코딩&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(M)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;M = 메시지 심볼 수&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;디코딩&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(M * depth)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;depth &amp;#x3C;= N, 평균 O(log N)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;최적성-증명&quot;&gt;최적성 증명&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Sibling property&lt;/strong&gt;: 같은 부모의 두 자식은 항상 heap 에서 가장 낮은 두 원소. 이 성질이 최적 부호를 그리디하게 유도.&lt;/p&gt;
&lt;p&gt;귀납 스케치: N=2 는 자명. N&gt;2 일 때, 가장 낮은 두 원소를 합치는 것이 &lt;strong&gt;최적 트리에서도 같은 깊이의 형제&lt;/strong&gt;임을 Sibling lemma 로 보임. 합친 노드를 단일 원소로 바꾸면 N-1 인스턴스가 되어 귀납 적용.&lt;/p&gt;
&lt;h2 id=&quot;응용&quot;&gt;응용&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DEFLATE (gzip, PNG)&lt;/strong&gt;: LZ77 + Huffman 결합. 가장 널리 사용되는 압축 조합&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JPEG&lt;/strong&gt;: DCT 계수를 Huffman 으로 부호화&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTTP/2 [[hpack|HPACK]]&lt;/strong&gt;: 헤더 압축에 정적 Huffman 테이블 사용&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MP3&lt;/strong&gt;: 오디오 스펙트럼 계수 부호화&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;함정&quot;&gt;함정&lt;/h2&gt;
&lt;h3 id=&quot;1-단일-심볼-입력&quot;&gt;1. 단일 심볼 입력&lt;/h3&gt;
&lt;p&gt;심볼이 1개면 트리가 루트만 있어 부호가 빈 문자열. 별도 처리(길이 1 부호 강제) 필요.&lt;/p&gt;
&lt;h3 id=&quot;2-정적-빈도-가정&quot;&gt;2. 정적 빈도 가정&lt;/h3&gt;
&lt;p&gt;표준 Huffman 은 빈도를 &lt;strong&gt;미리&lt;/strong&gt; 알아야 함. 파일 전체를 두 번 읽는다 (1회: 빈도 계산, 2회: 부호화). 스트리밍 환경에는 &lt;strong&gt;Adaptive Huffman (FGK, Vitter 알고리즘)&lt;/strong&gt; 사용.&lt;/p&gt;
&lt;h3 id=&quot;3-확률이-극단적일-때-비효율&quot;&gt;3. 확률이 극단적일 때 비효율&lt;/h3&gt;
&lt;p&gt;한 심볼 확률이 0.99 이면 Huffman 은 1비트를 할당하지만, Shannon entropy H ≈ 0.08 bit. &lt;strong&gt;Arithmetic Coding&lt;/strong&gt; 이 entropy 에 더 근접한 압축을 달성.&lt;/p&gt;
&lt;h3 id=&quot;4-코드테이블-저장-비용&quot;&gt;4. 코드테이블 저장 비용&lt;/h3&gt;
&lt;p&gt;압축 파일에 Huffman 코드테이블도 같이 저장해야 함. 심볼 종류가 많거나 메시지가 짧으면 오히려 용량이 늘 수 있음.&lt;/p&gt;
&lt;h3 id=&quot;5-부동소수점-빈도-주의&quot;&gt;5. 부동소수점 빈도 주의&lt;/h3&gt;
&lt;p&gt;Python &lt;code&gt;heapq&lt;/code&gt; 나 C++ &lt;code&gt;priority_queue&lt;/code&gt; 에서 부동소수점 빈도를 쓰면 동률 처리가 불안정해져 같은 입력에 다른 트리가 나올 수 있음. 정수로 변환하거나 타이브레이킹 기준을 명시하는 것이 안전.&lt;/p&gt;
&lt;h2 id=&quot;boj-연습-문제&quot;&gt;BOJ 연습 문제&lt;/h2&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;번호&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;제목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;핵심&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1715&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;카드 정렬하기&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Huffman coding 직접 응용, 합병 비용 최소화&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 13904&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;과제&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;그리디 + 우선순위 큐&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 19598&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;최소 회의실 개수&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;min-heap 활용&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;관련 [[priority-queue-heap|Priority Queue]]&lt;/li&gt;
&lt;li&gt;관련 [[hpack|HPACK]] (HTTP/2 헤더 압축)&lt;/li&gt;
&lt;li&gt;관련 [[greedy|Greedy]]&lt;/li&gt;
&lt;li&gt;Huffman, “A Method for the Construction of Minimum-Redundancy Codes” (1952)&lt;/li&gt;
&lt;li&gt;Shannon, “A Mathematical Theory of Communication” (1948)&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>algorithm</category><category>foundation</category><category>greedy</category><category>compression</category><category>tree</category><author>koa (김신건)</author></item><item><title>Inclusion and Exclusion: 포함배제 원리</title><link>https://shinkeonkim.com/wiki/algorithm/math/inclusion-exclusion/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/algorithm/math/inclusion-exclusion/</guid><pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;포함배제 원리 (Principle of Inclusion-Exclusion, PIE)&lt;/strong&gt; 는 합집합의 크기를 교집합의 크기들을 이용해 계산하는 조합론의 기본 원리입니다.&lt;/p&gt;
&lt;p&gt;$$
\left| \bigcup_{i=1}^{n} A_i \right| = \sum_{k=1}^{n} (-1)^{k-1} \sum_{|S|=k} \left| \bigcap_{i \in S} A_i \right|
$$&lt;/p&gt;
&lt;p&gt;부호가 집합 크기에 따라 교대로 바뀝니다: 홀수 크기 부분집합은 더하고, 짝수 크기 부분집합은 뺍니다.&lt;/p&gt;
&lt;p&gt;3개 집합의 경우:&lt;/p&gt;
&lt;p&gt;$$
|A \cup B \cup C| = |A| + |B| + |C| - |A \cap B| - |A \cap C| - |B \cap C| + |A \cap B \cap C|
$$&lt;/p&gt;
&lt;h2 id=&quot;문제-상황과-동기&quot;&gt;문제 상황과 동기&lt;/h2&gt;
&lt;h3 id=&quot;직접-세기가-어려운-경우&quot;&gt;직접 세기가 어려운 경우&lt;/h3&gt;
&lt;p&gt;“적어도 하나의 조건을 만족하는 경우의 수”는 조건들 간 중복이 있어 직접 세기가 어렵습니다. PIE는 이런 중복을 체계적으로 처리합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;자연수 범위 계수&lt;/strong&gt;: [1, n] 중 소수 p1, p2, …, pk 중 적어도 하나의 배수 수&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;금지 위치 순열&lt;/strong&gt;: 특정 위치에 특정 원소가 와서는 안 되는 경우의 수&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;교란 순열 (Derangement)&lt;/strong&gt;: 어떤 원소도 원래 위치에 없는 순열 수&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;오일러 파이 함수&lt;/strong&gt;: n 이하 서로소 수&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;직관적-이해&quot;&gt;직관적 이해&lt;/h3&gt;
&lt;p&gt;3개 원소 A, B, C 가 겹칠 때 단순 합산하면:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A와 B 공통 원소는 2번 계산됨 (빼야 함)&lt;/li&gt;
&lt;li&gt;A, B, C 모두 공통 원소는 3번 더하고 3번 빼서 0번 계산됨 (더해야 함)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;피차 조정을 반복하면 정확히 1회씩 세는 것이 PIE의 핵심입니다.&lt;/p&gt;
&lt;h2 id=&quot;시각화&quot;&gt;시각화&lt;/h2&gt;
&lt;h3 id=&quot;포함배제-단계별-조정&quot;&gt;포함배제 단계별 조정&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S1[&quot;1단계: 각 집합 크기 합산 (중복 포함)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S2[&quot;2단계: 2개 교집합 차감 (초과 차감)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S3[&quot;3단계: 3개 교집합 추가 (정확한 값)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Result[&quot;합집합 크기 = 정확한 원소 수&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S1 --&gt; S2 --&gt; S3 --&gt; Result&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;집합-구조와-교집합&quot;&gt;집합 구조와 교집합&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A[&quot;집합 A&quot;] --&gt; AB[&quot;A, B 교집합&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B[&quot;집합 B&quot;] --&gt; AB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A --&gt; AC[&quot;A, C 교집합&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C[&quot;집합 C&quot;] --&gt; AC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B --&gt; BC[&quot;B, C 교집합&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C --&gt; BC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    AB --&gt; ABC[&quot;A, B, C 교집합&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    BC --&gt; ABC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    style AB fill:#ffcccc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    style AC fill:#ffcccc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    style BC fill:#ffcccc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    style ABC fill:#ccffcc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;교집합들은 각각 부호를 바꿔가며 더하고 뺍니다.&lt;/p&gt;
&lt;h2 id=&quot;핵심-아이디어&quot;&gt;핵심 아이디어&lt;/h2&gt;
&lt;h3 id=&quot;비트마스크-열거&quot;&gt;비트마스크 열거&lt;/h3&gt;
&lt;p&gt;n 개의 집합이 있을 때, 모든 부분집합을 비트마스크로 열거해 PIE를 계산합니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;result = 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for mask = 1 to (1&amp;#x3C;&amp;#x3C;n)-1:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    bits = popcount(mask)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    val = intersection size of sets in mask&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if bits % 2 == 1: result += val   # 홀수 크기: 더하기&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    else: result -= val               # 짝수 크기: 빼기&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;n 개의 집합 대상으로 $O(2^n)$ 부분집합 열거. n &amp;#x3C;= 20 정도에서 실용적.&lt;/p&gt;
&lt;h3 id=&quot;응용-패턴-배수-계수&quot;&gt;응용 패턴: 배수 계수&lt;/h3&gt;
&lt;p&gt;n 이하에서 소수 집합 ${p_1, …, p_k}$ 중 적어도 하나의 배수 수:&lt;/p&gt;
&lt;p&gt;$$
\sum_{S \subseteq {p_1,…,p_k}} (-1)^{|S|+1} \left\lfloor \frac{n}{\prod_{p \in S} p} \right\rfloor
$$&lt;/p&gt;
&lt;p&gt;공집합 제외, 부호는 집합 크기 홀/짝에 따라 +/-.&lt;/p&gt;
&lt;h3 id=&quot;응용-패턴-교란-순열&quot;&gt;응용 패턴: 교란 순열&lt;/h3&gt;
&lt;p&gt;아무 원소도 원래 위치에 없는 순열 수 $D_n$:&lt;/p&gt;
&lt;p&gt;$$
D_n = n! \sum_{k=0}^{n} \frac{(-1)^k}{k!} \approx \frac{n!}{e}
$$&lt;/p&gt;
&lt;p&gt;점화식으로도 계산 가능: $D_n = (n-1)(D_{n-1} + D_{n-2})$, $D_0 = 1$, $D_1 = 0$.&lt;/p&gt;
&lt;h3 id=&quot;응용-패턴-오일러-파이-함수&quot;&gt;응용 패턴: 오일러 파이 함수&lt;/h3&gt;
&lt;p&gt;n 이하에서 n 과 서로소인 수의 개수:&lt;/p&gt;
&lt;p&gt;$$
\varphi(n) = n \prod_{p \mid n} \left(1 - \frac{1}{p}\right)
$$&lt;/p&gt;
&lt;p&gt;소인수 집합으로 PIE 적용한 것과 동일합니다.&lt;/p&gt;
&lt;h2 id=&quot;알고리즘&quot;&gt;알고리즘&lt;/h2&gt;
&lt;h3 id=&quot;비트마스크-pie-n-집합-배수-계수&quot;&gt;비트마스크 PIE (n 집합 배수 계수)&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;PIE(n, primes[k]):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    result = 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for mask = 1 to (1&amp;#x3C;&amp;#x3C;k)-1:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        prod = 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        bits = 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        for i = 0..k-1:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            if mask &gt;&gt; i &amp;#x26; 1:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                prod *= primes[i]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                bits++&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        if bits % 2 == 1: result += n / prod&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        else: result -= n / prod&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return result  # n 이하에서 primes 중 하나라도 배수인 수의 개수&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;교란-순열-점화식&quot;&gt;교란 순열 점화식&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;D(0) = 1, D(1) = 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;D(n) = (n-1) * (D(n-1) + D(n-2))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;구현&quot;&gt;구현&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
variants={[
{
language: “cpp”,
label: “C++”,
code: `// 포함배제 원리: 배수 계수 + 교란 순열
#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;&lt;/p&gt;
&lt;p&gt;// [1, n] 중 primes 중 적어도 하나의 배수 수 (PIE)
long long count_multiples(long long n, vector&lt;long long=&quot;&quot;&gt; primes) {
int k = primes.size();
long long res = 0;
for (int mask = 1; mask &amp;#x3C; (1 &amp;#x3C;&amp;#x3C; k); mask++) {
long long prod = 1;
int bits = __builtin_popcount(mask);
for (int i = 0; i &amp;#x3C; k; i++)
if (mask &gt;&gt; i &amp;#x26; 1) prod *= primes[i];
if (bits % 2 == 1) res += n / prod;
else               res -= n / prod;
}
return res;
}&lt;/long&gt;&lt;/p&gt;
&lt;p&gt;// 교란 순열 D(n): 아무 원소도 원래 위치에 없는 순열 수
long long derangement(int n) {
if (n == 0) return 1;
if (n == 1) return 0;
long long a = 1, b = 0;  // D(0), D(1)
for (int i = 2; i &amp;#x3C;= n; i++) {
long long c = (long long)(i-1) * (a + b);
a = b; b = c;
}
return b;
}&lt;/p&gt;
&lt;p&gt;// 오일러 파이 함수 phi(n): n 이하 n과 서로소 수 개수
long long euler_phi(long long n) {
long long result = n;
for (long long p = 2; p * p &amp;#x3C;= n; p++) {
if (n % p == 0) {
while (n % p == 0) n /= p;
result -= result / p;
}
}
if (n &gt; 1) result -= result / n;
return result;
}&lt;/p&gt;
&lt;p&gt;int main() {
// 예시 1: [1, 30] 중 2, 3, 5 의 배수 수 (PIE)
vector&lt;long long=&quot;&quot;&gt; primes = {2, 3, 5};
long long cnt = count_multiples(30, primes);
cout &amp;#x3C;&amp;#x3C; “[1,30] 중 2,3,5 의 배수 수: ” &amp;#x3C;&amp;#x3C; cnt &amp;#x3C;&amp;#x3C; “\n”;  // 22&lt;/long&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 예시 2: [1, 30] 중 2, 3, 5 의 배수가 아닌 수&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cout &amp;#x3C;&amp;#x3C; &quot;[1,30] 중 2,3,5 의 배수 아닌 수: &quot; &amp;#x3C;&amp;#x3C; 30 - cnt &amp;#x3C;&amp;#x3C; &quot;\\n&quot;;  // 8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 예시 3: 교란 순열&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for (int i = 1; i &amp;#x3C;= 6; i++)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    cout &amp;#x3C;&amp;#x3C; &quot;D(&quot; &amp;#x3C;&amp;#x3C; i &amp;#x3C;&amp;#x3C; &quot;) = &quot; &amp;#x3C;&amp;#x3C; derangement(i) &amp;#x3C;&amp;#x3C; &quot;\\n&quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 예시 4: 오일러 파이&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cout &amp;#x3C;&amp;#x3C; &quot;phi(12) = &quot; &amp;#x3C;&amp;#x3C; euler_phi(12) &amp;#x3C;&amp;#x3C; &quot;\\n&quot;;  // 4 ({1,5,7,11})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;return 0;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;code&gt;,     },     {       language: &quot;python&quot;,       label: &quot;Python&quot;,       code: &lt;/code&gt;def count_multiples(n, primes):
&quot;&quot;&quot;PIE: [1, n] 중 primes 중 적어도 하나의 배수 수&quot;&quot;&quot;
k = len(primes)
res = 0
for mask in range(1, 1 &amp;#x3C;&amp;#x3C; k):
bits = bin(mask).count(‘1’)
prod = 1
for i in range(k):
if mask &gt;&gt; i &amp;#x26; 1:
prod *= primes[i]
if bits % 2 == 1:
res += n // prod
else:
res -= n // prod
return res&lt;/p&gt;
&lt;p&gt;def derangement(n):
&quot;&quot;&quot;교란 순열 D(n): (n-1)*(D(n-1)+D(n-2)) 점화식&quot;&quot;&quot;
if n == 0: return 1
if n == 1: return 0
a, b = 1, 0  # D(0), D(1)
for i in range(2, n+1):
a, b = b, (i-1) * (a + b)
return b&lt;/p&gt;
&lt;p&gt;def euler_phi(n):
&quot;&quot;&quot;오일러 파이 함수: n 이하 n과 서로소 수 개수&quot;&quot;&quot;
result = n
p = 2
temp = n
while p * p &amp;#x3C;= temp:
if temp % p == 0:
while temp % p == 0:
temp //= p
result -= result // p
p += 1
if temp &gt; 1:
result -= result // temp
return result&lt;/p&gt;
&lt;h1 id=&quot;예시-1-1-30-중-2-3-5-의-배수-수&quot;&gt;예시 1: [1, 30] 중 2, 3, 5 의 배수 수&lt;/h1&gt;
&lt;p&gt;primes = [2, 3, 5]
cnt = count_multiples(30, primes)
print(f”[1,30] 중 2,3,5 의 배수 수: {cnt}”)        # 22
print(f”[1,30] 중 2,3,5 의 배수 아닌 수: {30-cnt}”) # 8&lt;/p&gt;
&lt;h1 id=&quot;예시-2-교란-순열&quot;&gt;예시 2: 교란 순열&lt;/h1&gt;
&lt;p&gt;for i in range(1, 7):
print(f”D({i}) = {derangement(i)}“)&lt;/p&gt;
&lt;h1 id=&quot;예시-3-오일러-파이&quot;&gt;예시 3: 오일러 파이&lt;/h1&gt;
&lt;p&gt;print(f”phi(12) = {euler_phi(12)}”)  # 4`,
},
]}
cases={[
{
label: “[1,30] 중 2,3,5 배수 수”,
input: “n=30, primes=[2,3,5]”,
output: “[1,30] 중 2,3,5 의 배수 수: 22\n[1,30] 중 2,3,5 의 배수 아닌 수: 8”,
},
{
label: “교란 순열 D(1) - D(6)”,
input: “n = 1, 2, 3, 4, 5, 6”,
output: “D(1) = 0\nD(2) = 1\nD(3) = 2\nD(4) = 9\nD(5) = 44\nD(6) = 265”,
},
{
label: “오일러 파이 phi(12)”,
input: “n = 12”,
output: “phi(12) = 4”,
},
]}
/&gt;&lt;/p&gt;
&lt;h2 id=&quot;복잡도&quot;&gt;복잡도&lt;/h2&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;항목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;복잡도&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;조건&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;비트마스크 PIE&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(2^k)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;k = 집합 수&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;배수 계수&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(2^k)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;k = 소수 개수&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;교란 순열 점화식&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(n)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;오일러 파이&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(sqrt(n))&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;단일 n&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;k &amp;#x3C;= 20 이면 비트마스크 PIE 가능 (2^20 = 10^6). k &gt; 20 이면 다른 방법 (뫼비우스 함수 등) 필요.&lt;/p&gt;
&lt;h2 id=&quot;주요-응용-정리&quot;&gt;주요 응용 정리&lt;/h2&gt;
&lt;h3 id=&quot;금지-위치-순열-forbidden-positions&quot;&gt;금지 위치 순열 (Forbidden Positions)&lt;/h3&gt;
&lt;p&gt;n 개 원소, k 개의 금지 위치 쌍 (i, j) 이 있을 때 조건을 모두 피하는 순열 수:&lt;/p&gt;
&lt;p&gt;$$
\text{count} = \sum_{S \subseteq \text{forbidden}} (-1)^{|S|} \cdot (\text{S의 조건 동시 만족 순열 수})
$$&lt;/p&gt;
&lt;h3 id=&quot;derangement-공식-직접-pie&quot;&gt;Derangement 공식 (직접 PIE)&lt;/h3&gt;
&lt;p&gt;$$
D_n = \sum_{k=0}^{n} (-1)^k \binom{n}{k} (n-k)! = n! \sum_{k=0}^{n} \frac{(-1)^k}{k!}
$$&lt;/p&gt;
&lt;p&gt;k 개 원소가 제자리에 있는 경우를 PIE로 제거. $D_n \approx n!/e$ 근사.&lt;/p&gt;
&lt;h3 id=&quot;에라토스테네스-체-변형&quot;&gt;에라토스테네스 체 변형&lt;/h3&gt;
&lt;p&gt;[1, n] 중 집합 P의 소수들로 나누어지지 않는 수의 개수:&lt;/p&gt;
&lt;p&gt;$$
\left\lfloor n \right\rfloor - \sum_p \lfloor n/p \rfloor + \sum_{p&amp;#x3C;q} \lfloor n/pq \rfloor - \cdots
$$&lt;/p&gt;
&lt;p&gt;[[sieve|에라토스테네스 체]] 와 PIE 조합.&lt;/p&gt;
&lt;h2 id=&quot;함정&quot;&gt;함정&lt;/h2&gt;
&lt;h3 id=&quot;1-공집합-마스크-포함-오류&quot;&gt;1. 공집합 마스크 포함 오류&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
mask = 0 (공집합) 은 교집합 크기가 전체 n 이므로 PIE 합산에서 제외해야 합니다. &lt;code&gt;for mask = 1 to ...&lt;/code&gt; 로 시작.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;2-부호-혼동-홀수짝수-반전&quot;&gt;2. 부호 혼동 (홀수/짝수 반전)&lt;/h3&gt;
&lt;p&gt;홀수 크기 부분집합은 &lt;strong&gt;더하고&lt;/strong&gt; (부호 +), 짝수 크기는 &lt;strong&gt;빼는&lt;/strong&gt; (부호 -) 것이 기본. “합집합에서 빼야 할 것”을 구하는 문제에서 부호가 반전됨 주의.&lt;/p&gt;
&lt;h3 id=&quot;3-곱이-n-초과-시-0-처리&quot;&gt;3. 곱이 n 초과 시 0 처리&lt;/h3&gt;
&lt;p&gt;배수 계수에서 &lt;code&gt;prod &gt; n&lt;/code&gt; 이면 &lt;code&gt;n / prod = 0&lt;/code&gt;. 곱 계산 시 &lt;code&gt;prod &gt; n&lt;/code&gt; 체크로 조기 스킵 가능.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; k; i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (mask &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (prod &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; primes[i]) { prod &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;break&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; }&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // 오버플로우 방지&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        prod &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; primes[i];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (prod &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;continue&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;4-대규모-k-에서-2k-폭발&quot;&gt;4. 대규모 k 에서 2^k 폭발&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
k &gt;= 30 이면 2^k &gt; 10^9 로 비트마스크 PIE가 불가능합니다. [[mobius-function|뫼비우스 함수]] 기반 알고리즘으로 전환.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;5-교란-순열-오버플로우&quot;&gt;5. 교란 순열 오버플로우&lt;/h3&gt;
&lt;p&gt;$D_{20} \approx 8.95 \times 10^{17}$, long long 한계 (약 $9.2 \times 10^{18}$) 이내. $D_{21}$ 부터 unsigned long long 또는 모듈러 연산 필요.&lt;/p&gt;
&lt;h3 id=&quot;6-오일러-파이-vs-배수-계수-혼동&quot;&gt;6. 오일러 파이 vs 배수 계수 혼동&lt;/h3&gt;
&lt;p&gt;$\varphi(n)$ 은 n 이하 n 과 서로소인 수. count_multiples 는 [1, n] 중 특정 소수 배수. 두 공식이 비슷해 보여도 적용 문맥이 다릅니다.&lt;/p&gt;
&lt;h2 id=&quot;고급-응용&quot;&gt;고급 응용&lt;/h2&gt;
&lt;h3 id=&quot;lgv-정리와의-연결&quot;&gt;LGV 정리와의 연결&lt;/h3&gt;
&lt;p&gt;격자 경로 계수에서 PIE를 체계화한 것이 [[lgv-theorem|Lindstrom-Gessel-Viennot 정리]].&lt;/p&gt;
&lt;h3 id=&quot;뫼비우스-함수&quot;&gt;뫼비우스 함수&lt;/h3&gt;
&lt;p&gt;[[mobius-function|뫼비우스 함수]] $\mu(n)$ 은 PIE의 수론적 일반화. $\mu(n) = (-1)^k$ (k = 서로 다른 소인수 수), $\mu(n) = 0$ (중복 소인수 있으면).&lt;/p&gt;
&lt;h3 id=&quot;burnsides-lemma&quot;&gt;Burnside’s Lemma&lt;/h3&gt;
&lt;p&gt;대칭을 고려한 계수에서 PIE의 변형. 군론 기반.&lt;/p&gt;
&lt;h2 id=&quot;boj-연습-문제&quot;&gt;BOJ 연습 문제&lt;/h2&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;번호&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;제목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;유형&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1947&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;선물 전달&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;교란 순열&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1735&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;분수 합산&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;서로소&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1036&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;36진수&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;배수 계수&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1456&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;거의 소수&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;에라토스테네스 + 배수&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 2960&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;에라토스테네스의 체&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;배수 계수&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 16565&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;N포커&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;이항계수 + PIE&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 14182&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;쌍의 합&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;집합 계수 PIE&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[euler-phi-function|Euler Phi Function]] (PIE 응용 대표)&lt;/li&gt;
&lt;li&gt;[[combinatorics|조합론]] (계수 기초)&lt;/li&gt;
&lt;li&gt;[[mobius-function|뫼비우스 함수]] (PIE 수론 일반화)&lt;/li&gt;
&lt;li&gt;[[number-theory|수론]] (배수/약수 이론)&lt;/li&gt;
&lt;li&gt;[[sieve|에라토스테네스 체]] (소수 배수 열거)&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>algorithm</category><category>math</category><category>combinatorics</category><category>counting</category><category>mobius</category><author>koa (김신건)</author></item><item><title>Insertion Sort</title><link>https://shinkeonkim.com/wiki/algorithm/insertion-sort/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/algorithm/insertion-sort/</guid><pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Insertion Sort (삽입 정렬)&lt;/strong&gt; 는 한 번에 한 원소씩 &lt;em&gt;이미 정렬된 부분에 올바른 위치를 찾아 삽입&lt;/em&gt; 하는 정렬. 카드 정리하듯이 한 장씩 적절한 자리에 끼워넣는 동작.&lt;/p&gt;
&lt;p&gt;O(n²) 알고리즘 중 &lt;strong&gt;실측 가장 빠르다&lt;/strong&gt;. 작은 입력 (n ≤ 16) 이나 거의 정렬된 입력에서 Quick Sort 같은 O(n log n) 알고리즘보다도 빠를 수 있다. 그래서 &lt;strong&gt;Timsort, Introsort, Pdqsort&lt;/strong&gt; 가 부분 정렬에 모두 Insertion Sort 를 사용한다.&lt;/p&gt;
&lt;p&gt;전체 비교는 [[정렬 알고리즘]] 참고.&lt;/p&gt;
&lt;h2 id=&quot;시각화&quot;&gt;시각화&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;key 를 찾아 올바른 자리에 끼워넣는 과정:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Start[&quot;i=1&quot;] --&gt; Key[&quot;key = arr[i], j = i-1&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Key --&gt; CMP{&quot;j &gt;= 0 AND arr[j] &gt; key?&quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    CMP --&gt;|&quot;Yes&quot;| Shift[&quot;arr[j+1] = arr[j], j--&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Shift --&gt; CMP&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    CMP --&gt;|&quot;No&quot;| Insert[&quot;arr[j+1] = key&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Insert --&gt; Inc{&quot;i &amp;#x3C; n?&quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Inc --&gt;|&quot;Yes&quot;| Key2[&quot;i++, 다음 원소&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Inc --&gt;|&quot;No&quot;| Done[&quot;정렬 완료&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Key2 --&gt; Key&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;알고리즘&quot;&gt;알고리즘&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;insertionSort(arr):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  for i = 1 to n-1:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    key = arr[i]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    j = i - 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    while j &gt;= 0 and arr[j] &gt; key:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      arr[j + 1] = arr[j]  // 한 칸씩 오른쪽으로 밀기&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      j--&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    arr[j + 1] = key       // 빈 자리에 삽입&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;핵심-동작&quot;&gt;핵심 동작&lt;/h3&gt;
&lt;p&gt;배열 앞부분이 &lt;em&gt;이미 정렬된 영역&lt;/em&gt;, 나머지가 &lt;em&gt;미처리 영역&lt;/em&gt;. 미처리에서 하나씩 꺼내 정렬된 영역의 올바른 위치에 삽입.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[ 정렬됨 | 미처리 ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[ 5    | 2, 8, 1, 4 ]   초기&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[ 2, 5 | 8, 1, 4 ]      2 를 5 앞에 삽입&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[ 2, 5, 8 | 1, 4 ]      8 은 이미 5 보다 큼, 그 자리에&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[ 1, 2, 5, 8 | 4 ]      1 을 맨 앞으로&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[ 1, 2, 4, 5, 8 | ]     4 를 5 앞에 삽입&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;매 단계에서 정렬된 영역이 정확히 정렬 상태를 유지.&lt;/p&gt;
&lt;h2 id=&quot;구현&quot;&gt;구현&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
variants={[
{
language: “cpp”,
label: “C++”,
code: `#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;&lt;/p&gt;
&lt;p&gt;void insertionSort(vector&lt;int&gt;&amp;#x26; arr) {
int n = arr.size();
for (int i = 1; i &amp;#x3C; n; i++) {
int key = arr[i];
int j = i - 1;
while (j &gt;= 0 &amp;#x26;&amp;#x26; arr[j] &gt; key) {
arr[j+1] = arr[j];
j—;
}
arr[j+1] = key;
}
}&lt;/int&gt;&lt;/p&gt;
&lt;p&gt;int main() {
vector&lt;int&gt; arr = {12, 11, 13, 5, 6};
cout &amp;#x3C;&amp;#x3C; “Before: ”;
for (int x : arr) cout &amp;#x3C;&amp;#x3C; x &amp;#x3C;&amp;#x3C; ” ”;
cout &amp;#x3C;&amp;#x3C; “\n”;
insertionSort(arr);
cout &amp;#x3C;&amp;#x3C; “After:  ”;
for (int x : arr) cout &amp;#x3C;&amp;#x3C; x &amp;#x3C;&amp;#x3C; ” ”;
cout &amp;#x3C;&amp;#x3C; “\n”;
// 역순 입력 (최악)
vector&lt;int&gt; rev = {5, 4, 3, 2, 1};
insertionSort(rev);
cout &amp;#x3C;&amp;#x3C; “Reversed: ”;
for (int x : rev) cout &amp;#x3C;&amp;#x3C; x &amp;#x3C;&amp;#x3C; ” ”;
cout &amp;#x3C;&amp;#x3C; “\n”;
}&lt;code&gt;,     },     {       language: &quot;python&quot;,       label: &quot;Python&quot;,       code: &lt;/code&gt;def insertion_sort(arr: list) -&gt; None:
for i in range(1, len(arr)):
key = arr[i]
j = i - 1
while j &gt;= 0 and arr[j] &gt; key:
arr[j+1] = arr[j]
j -= 1
arr[j+1] = key&lt;/int&gt;&lt;/int&gt;&lt;/p&gt;
&lt;p&gt;arr = [12, 11, 13, 5, 6]
print(“Before:”, arr)
insertion_sort(arr)
print(“After: ”, arr)&lt;/p&gt;
&lt;h1 id=&quot;거의-정렬된-입력-최선&quot;&gt;거의 정렬된 입력 (최선)&lt;/h1&gt;
&lt;p&gt;near_sorted = [1, 2, 4, 3, 5, 6]
insertion_sort(near_sorted)
print(“Nearly sorted:”, near_sorted)&lt;code&gt;,     },   ]}   cases={[     {       label: &quot;기본 실행&quot;,       input: &quot;&quot;,       output: &lt;/code&gt;Before: 12 11 13 5 6
After:  5 6 11 12 13
Reversed: 1 2 3 4 5`,
},
]}
/&gt;&lt;/p&gt;
&lt;h2 id=&quot;복잡도&quot;&gt;복잡도&lt;/h2&gt;





































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;항목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;값&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;시간 (최선)&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;O(n)&lt;/strong&gt; (이미 정렬됨)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;시간 (평균)&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(n²)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;시간 (최악)&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(n²) (역순 입력)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;공간&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(1)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;안정성&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓ Stable&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;In-place&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Online&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓ (한 번에 한 원소씩 처리 가능)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;왜-on-중-가장-빠른가&quot;&gt;왜 O(n²) 중 가장 빠른가&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;상수항 작음&lt;/strong&gt;: inner loop 가 단순한 비교 + 이동 한 번&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;캐시 친화&lt;/strong&gt;: 인접 메모리 접근만&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;거의 정렬된 입력에 O(n)&lt;/strong&gt; 에 가깝게 동작&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;분기 예측 친화적&lt;/strong&gt;: 조건이 단순&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;정렬된-부분의-활용&quot;&gt;정렬된 부분의 활용&lt;/h2&gt;
&lt;p&gt;Insertion Sort 의 최대 장점은 &lt;strong&gt;입력의 부분적 정렬을 직접 활용&lt;/strong&gt; 한다는 것.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nearSorted&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;6&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;7&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;9&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 5, 4 만 어긋남&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// insertion sort: 4 를 한 칸 이동 → 1 swap, 끝&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// O(n) 실측&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 성질이 &lt;strong&gt;Timsort&lt;/strong&gt; (Python &lt;code&gt;sorted&lt;/code&gt;, Java &lt;code&gt;Arrays.sort&lt;/code&gt;) 의 기반:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;입력에서 자연스러운 run (정렬된 부분) 식별&lt;/li&gt;
&lt;li&gt;짧은 run 은 Insertion Sort 로 길이 맞춤&lt;/li&gt;
&lt;li&gt;run 들을 Merge Sort 로 합침&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;하이브리드-알고리즘에서의-역할&quot;&gt;하이브리드 알고리즘에서의 역할&lt;/h2&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;알고리즘&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Insertion Sort 사용 시점&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Timsort&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;짧은 run 정렬 (n &amp;#x3C; 64)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Introsort&lt;/strong&gt; (C++ &lt;code&gt;std::sort&lt;/code&gt;)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;작은 부분 (n ≤ 16)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Pdqsort&lt;/strong&gt; (Rust)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;작은 부분&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Dual-Pivot QS&lt;/strong&gt; (Java)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;작은 부분&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;strong&gt;n 이 작을 때는 O(n²) 가 O(n log n) 보다 빠르다.&lt;/strong&gt; 상수항이 결정적. n=20 에서 Insertion 의 비교 횟수 (~200) 가 Quick 의 함수 호출 오버헤드보다 적다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;binary-insertion-sort&quot;&gt;Binary Insertion Sort&lt;/h2&gt;
&lt;p&gt;삽입 위치를 &lt;strong&gt;이진 탐색&lt;/strong&gt; 으로 찾음. 비교 횟수는 O(n log n) 이지만 &lt;strong&gt;이동 횟수는 여전히 O(n²)&lt;/strong&gt;. 비교 비용이 비싼 경우 (예: 긴 문자열) 에 유리.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;일반: O(n) 비교 × O(n) = O(n²)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;이진: O(log n) 비교 × O(n) = O(n log n) 비교&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;       O(n) 이동 × O(n) = O(n²) 이동&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;총 비용은 여전히 O(n²) 지만 비교 횟수가 줄어든다.&lt;/p&gt;
&lt;h2 id=&quot;bubble--selection-과의-비교&quot;&gt;Bubble / Selection 과의 비교&lt;/h2&gt;









































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;항목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Insertion&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Bubble&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Selection&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;실측 속도&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;가장 빠름&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;가장 느림&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;중간&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;안정성&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;거의 정렬된 입력&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;O(n)&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(n) (early exit)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(n²)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;비교 횟수&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(n²)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(n²)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(n²)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;교환 횟수&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(n²)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(n²)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;O(n)&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;실무-활용-의외로-자주&quot;&gt;실무 활용 (의외로 자주)&lt;/h2&gt;
&lt;p&gt;Insertion Sort 자체로는 작은 입력에 한정되지만, &lt;strong&gt;하이브리드 알고리즘의 부속품&lt;/strong&gt; 으로 거의 모든 표준 라이브러리에 들어가 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python &lt;code&gt;sorted()&lt;/code&gt;, &lt;code&gt;list.sort()&lt;/code&gt; → Timsort 내부&lt;/li&gt;
&lt;li&gt;Java &lt;code&gt;Arrays.sort()&lt;/code&gt; (객체) → Timsort 내부&lt;/li&gt;
&lt;li&gt;C++ &lt;code&gt;std::sort&lt;/code&gt; → Introsort 내부 (n ≤ 16 에서)&lt;/li&gt;
&lt;li&gt;Rust &lt;code&gt;slice::sort_unstable&lt;/code&gt; → Pdqsort 내부&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;n=8 같은 작은 배열을 정렬할 일이 있으면 Insertion Sort 가 &lt;strong&gt;올바른 선택&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;함정&quot;&gt;함정&lt;/h2&gt;
&lt;h3 id=&quot;1-삽입-과-교환-혼동&quot;&gt;1. “삽입” 과 “교환” 혼동&lt;/h3&gt;
&lt;p&gt;Insertion Sort 는 원소를 &lt;strong&gt;shift (이동)&lt;/strong&gt; 하지 교환하지 않는다. &lt;code&gt;arr[j+1] = arr[j]&lt;/code&gt; 로 한 칸씩 밀고, 마지막에 &lt;code&gt;arr[j+1] = key&lt;/code&gt; 로 삽입. Bubble Sort 처럼 &lt;code&gt;swap&lt;/code&gt; 을 반복하는 것보다 효율적 (이동 1회 vs swap 3회).&lt;/p&gt;
&lt;h3 id=&quot;2-인덱스-경계&quot;&gt;2. 인덱스 경계&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;j &gt;= 0&lt;/code&gt; 조건을 반드시 확인. &lt;code&gt;arr[j]&lt;/code&gt; 가 범위를 벗어나기 전에 while 루프를 종료해야 함. C++ 에서 &lt;code&gt;j&lt;/code&gt; 가 &lt;code&gt;int&lt;/code&gt; 가 아닌 &lt;code&gt;size_t&lt;/code&gt; (unsigned) 이면 &lt;code&gt;j &gt;= 0&lt;/code&gt; 가 항상 true 가 되어 무한 루프.&lt;/p&gt;
&lt;h3 id=&quot;3-역순-입력이-최악&quot;&gt;3. 역순 입력이 최악&lt;/h3&gt;
&lt;p&gt;역순 배열에서 i번째 원소를 삽입하려면 최대 i번 shift. 총 n*(n-1)/2 shift = O(n^2). Bubble Sort 와 같은 최악이지만 &lt;strong&gt;shift 연산이 swap 보다 빠르므로&lt;/strong&gt; 실측에서는 Bubble 보다 빠름.&lt;/p&gt;
&lt;h3 id=&quot;4-비교-비용이-비싼-경우&quot;&gt;4. 비교 비용이 비싼 경우&lt;/h3&gt;
&lt;p&gt;원소 비교 비용이 크면 (예: 긴 문자열, 복합 키) Binary Insertion Sort 가 유리: 이진 탐색으로 삽입 위치를 O(log i) 에 결정. 이동 횟수는 그대로 O(n^2) 이지만 비교 횟수가 O(n log n) 으로 줄어듦.&lt;/p&gt;
&lt;h3 id=&quot;5-안정-정렬-보장-조건&quot;&gt;5. 안정 정렬 보장 조건&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;arr[j] &gt; key&lt;/code&gt; (strict) 비교를 유지해야 stable. &lt;code&gt;arr[j] &gt;= key&lt;/code&gt; 로 바꾸면 같은 키 원소의 상대 순서가 뒤바뀜.&lt;/p&gt;
&lt;h2 id=&quot;boj-연습-문제&quot;&gt;BOJ 연습 문제&lt;/h2&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;번호&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;제목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;연관&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 2750&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;수 정렬하기&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;정렬 기본 (Insertion Sort 직접 구현)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1377&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;버블 소트 프로그램 1&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;최대 shift 횟수 = 원소가 왼쪽으로 이동한 최대 거리&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 11931&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;수 정렬하기 4&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;내림차순 정렬&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
BOJ 1377 은 Insertion Sort 변형 관점에서 접근 가능: 각 원소가 몇 칸 왼쪽으로 이동했는지의 최댓값이 answer. O(n^2) 시뮬레이션 대신 정렬 전후 위치 차이로 O(n log n) 풀이.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[정렬 알고리즘]]&lt;/li&gt;
&lt;li&gt;[[Bubble Sort]]&lt;/li&gt;
&lt;li&gt;[[Selection Sort]]&lt;/li&gt;
&lt;li&gt;[[Merge Sort]]&lt;/li&gt;
&lt;li&gt;[[Quick Sort]]&lt;/li&gt;
&lt;li&gt;Knuth, &lt;em&gt;TAOCP Vol. 3 §5.2.1&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>algorithm</category><category>sorting</category><category>education</category><author>koa (김신건)</author></item><item><title>Johnson&apos;s Algorithm: sparse APSP</title><link>https://shinkeonkim.com/wiki/algorithm/math/johnsons-algorithm/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/algorithm/math/johnsons-algorithm/</guid><pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Johnson’s algorithm&lt;/strong&gt;은 음수 간선이 있는 sparse graph의 all-pairs shortest paths (APSP)를 **O(VE log V)**에 계산. Bellman-Ford로 potential을 구한 뒤 간선을 재가중하여 Dijkstra를 V번 실행.&lt;/p&gt;
&lt;p&gt;$$
\text{복잡도: } O(VE \log V) \quad \text{(sparse graph에서 Floyd-Warshall O(V^3)보다 빠름)}
$$&lt;/p&gt;
&lt;h2 id=&quot;문제-상황&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p&gt;APSP 알고리즘 선택 기준:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Floyd-Warshall&lt;/strong&gt;: 음수 간선 허용, O(V³), dense graph에 적합&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dijkstra × V&lt;/strong&gt;: 음수 간선 불가, O(V(E + V) log V)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Johnson&lt;/strong&gt;: 음수 간선 허용, O(VE log V), sparse graph에 최적&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;음수 사이클이 있으면 최단 경로가 정의되지 않으므로 Bellman-Ford 단계에서 탐지.&lt;/p&gt;
&lt;h2 id=&quot;시각화&quot;&gt;시각화&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    G[&quot;원본 그래프 G (음수 간선 허용)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    step1[&quot;임시 정점 s 추가, 가중치 0 간선 연결&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    step2[&quot;Bellman-Ford 실행: h_v = dist(s, v)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    step3[&quot;간선 재가중: w&apos; = w + h_u - h_v&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    G2[&quot;재가중 그래프 G&apos; (모든 간선 w &gt;= 0)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    step4[&quot;각 정점 소스로 Dijkstra V회 실행&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    step5[&quot;거리 복원: d = d&apos; - h_u + h_v&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    APSP[&quot;모든 쌍 최단 경로 완성&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    G --&gt; step1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    step1 --&gt; step2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    step2 --&gt; step3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    step3 --&gt; G2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    G2 --&gt; step4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    step4 --&gt; step5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    step5 --&gt; APSP&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;핵심-아이디어&quot;&gt;핵심 아이디어&lt;/h2&gt;
&lt;h3 id=&quot;왜-dijkstra가-음수-간선에서-실패하는가&quot;&gt;왜 Dijkstra가 음수 간선에서 실패하는가&lt;/h3&gt;
&lt;p&gt;Dijkstra는 “이미 확정된 정점의 거리는 최소”라는 greedy 가정에 의존. 음수 간선이 있으면 나중에 더 짧은 경로가 발견될 수 있어 이 가정이 깨짐.&lt;/p&gt;
&lt;h3 id=&quot;재가중-reweighting의-핵심&quot;&gt;재가중 (Reweighting)의 핵심&lt;/h3&gt;
&lt;p&gt;potential 함수 h를 이용해 모든 간선을 비음수로 변환:&lt;/p&gt;
&lt;p&gt;$$
w’(u, v) = w(u, v) + h[u] - h[v]
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;성질 1&lt;/strong&gt;: $w’(u, v) \geq 0$ (Bellman-Ford 최단 경로 조건에 의해)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;성질 2&lt;/strong&gt;: 경로 $u \to v$의 재가중 거리 = 원래 거리 + h[u] - h[v]&lt;/p&gt;
&lt;p&gt;따라서 최단 경로의 순서는 변하지 않음. 재가중 후 Dijkstra로 구한 최단 경로를 원래 거리로 복원 가능.&lt;/p&gt;
&lt;h3 id=&quot;증명-wuv--0&quot;&gt;증명: w’(u,v) &gt;= 0&lt;/h3&gt;
&lt;p&gt;Bellman-Ford로 구한 h는 최단 경로 거리이므로:&lt;/p&gt;
&lt;p&gt;$$
h[v] \leq h[u] + w(u, v) \implies w(u, v) + h[u] - h[v] \geq 0
$$&lt;/p&gt;
&lt;h3 id=&quot;거리-복원&quot;&gt;거리 복원&lt;/h3&gt;
&lt;p&gt;재가중 그래프에서 Dijkstra로 구한 d’[u][v]에서 원래 거리 복원:&lt;/p&gt;
&lt;p&gt;$$
d[u][v] = d’[u][v] - h[u] + h[v]
$$&lt;/p&gt;
&lt;h2 id=&quot;알고리즘&quot;&gt;알고리즘&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Johnson&apos;s Algorithm&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1. 임시 정점 s 추가&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   for each vertex v: add edge (s, v) with weight 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;2. Bellman-Ford(s)로 h[v] = dist(s, v) 계산&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   if 음수 사이클 탐지: &quot;음수 사이클 존재&quot; 출력 후 종료&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;3. 간선 재가중&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   for each edge (u, v, w):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;       w&apos;(u, v) = w + h[u] - h[v]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;4. 각 정점 u에서 Dijkstra 실행 (재가중 그래프)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   d&apos;[u][v] = Dijkstra(u, G&apos;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;5. 원래 거리 복원&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   d[u][v] = d&apos;[u][v] - h[u] + h[v]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;구현&quot;&gt;구현&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
variants={[
{
language: “cpp”,
label: “C++”,
code: `#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;
typedef long long ll;
typedef pair&amp;#x3C;ll,int&gt; pli;
const ll INF = 1e18;&lt;/p&gt;
&lt;p&gt;int V, E;
vector&amp;#x3C;pair&amp;#x3C;int,ll&gt;&gt; adj[505];&lt;/p&gt;
&lt;p&gt;// Bellman-Ford: s에서 모든 정점까지 최단 거리
// 음수 사이클 있으면 false 반환
bool bellman_ford(int s, vector&lt;ll&gt;&amp;#x26; dist) {
dist.assign(V + 1, INF);
dist[s] = 0;
for (int i = 0; i &amp;#x3C; V; i++) {
for (int u = 0; u &amp;#x3C;= V; u++) {
if (dist[u] == INF) continue;
for (auto [v, w] : adj[u]) {
if (dist[u] + w &amp;#x3C; dist[v])
dist[v] = dist[u] + w;
}
}
}
for (int u = 0; u &amp;#x3C;= V; u++) {
if (dist[u] == INF) continue;
for (auto [v, w] : adj[u])
if (dist[u] + w &amp;#x3C; dist[v]) return false;
}
return true;
}&lt;/ll&gt;&lt;/p&gt;
&lt;p&gt;// Dijkstra: src에서 모든 정점까지 최단 거리
vector&lt;ll&gt; dijkstra(int src, vector&amp;#x3C;vector&amp;#x3C;pair&amp;#x3C;int,ll&gt;&gt;&gt;&amp;#x26; g) {
vector&lt;ll&gt; dist(V + 1, INF);
priority_queue&amp;#x3C;pli, vector&lt;pli&gt;, greater&lt;pli&gt;&gt; pq;
dist[src] = 0;
pq.push({0, src});
while (!pq.empty()) {
auto [d, u] = pq.top(); pq.pop();
if (d &gt; dist[u]) continue;
for (auto [v, w] : g[u]) {
if (dist[u] + w &amp;#x3C; dist[v]) {
dist[v] = dist[u] + w;
pq.push({dist[v], v});
}
}
}
return dist;
}&lt;/pli&gt;&lt;/pli&gt;&lt;/ll&gt;&lt;/ll&gt;&lt;/p&gt;
&lt;p&gt;int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cin &gt;&gt; V &gt;&gt; E;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for (int i = 0; i &amp;#x3C; E; i++) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    int u, v; ll w;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    cin &gt;&gt; u &gt;&gt; v &gt;&gt; w;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    adj[u].push_back({v, w});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 임시 정점 V: 모든 정점에 가중치 0 간선&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for (int v = 0; v &amp;#x3C; V; v++)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    adj[V].push_back({v, 0});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;vector&amp;#x3C;ll&gt; h;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;if (!bellman_ford(V, h)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    cout &amp;#x3C;&amp;#x3C; &quot;음수 사이클 존재\\n&quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return 0;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 재가중 그래프 구성&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;vector&amp;#x3C;vector&amp;#x3C;pair&amp;#x3C;int,ll&gt;&gt;&gt; g(V + 1);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for (int u = 0; u &amp;#x3C; V; u++)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for (auto [v, w] : adj[u])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        g[u].push_back({v, w + h[u] - h[v]});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 각 정점에서 Dijkstra 후 거리 복원&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for (int u = 0; u &amp;#x3C; V; u++) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    auto d = dijkstra(u, g);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for (int v = 0; v &amp;#x3C; V; v++) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        ll dist = (d[v] == INF) ? -1 : d[v] - h[u] + h[v];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        cout &amp;#x3C;&amp;#x3C; dist;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        if (v &amp;#x3C; V - 1) cout &amp;#x3C;&amp;#x3C; &quot; &quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    cout &amp;#x3C;&amp;#x3C; &quot;\\n&quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;return 0;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;code&gt;,     },     {       language: &quot;python&quot;,       label: &quot;Python&quot;,       code: &lt;/code&gt;import heapq
import sys
input = sys.stdin.readline
INF = float(‘inf’)&lt;/p&gt;
&lt;p&gt;def bellman_ford(s, V, adj):
dist = [INF] * (V + 1)
dist[s] = 0
for _ in range(V):
for u in range(V + 1):
if dist[u] == INF:
continue
for v, w in adj[u]:
if dist[u] + w &amp;#x3C; dist[v]:
dist[v] = dist[u] + w
for u in range(V + 1):
if dist[u] == INF:
continue
for v, w in adj[u]:
if dist[u] + w &amp;#x3C; dist[v]:
return None  # 음수 사이클
return dist&lt;/p&gt;
&lt;p&gt;def dijkstra(src, V, g):
dist = [INF] * (V + 1)
dist[src] = 0
pq = [(0, src)]
while pq:
d, u = heapq.heappop(pq)
if d &gt; dist[u]:
continue
for v, w in g[u]:
if dist[u] + w &amp;#x3C; dist[v]:
dist[v] = dist[u] + w
heapq.heappush(pq, (dist[v], v))
return dist&lt;/p&gt;
&lt;p&gt;def main():
V, E = map(int, input().split())
adj = [[] for _ in range(V + 1)]
for _ in range(E):
u, v, w = map(int, input().split())
adj[u].append((v, w))&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for v in range(V):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    adj[V].append((v, 0))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;h = bellman_ford(V, V, adj)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;if h is None:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    print(&quot;음수 사이클 존재&quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;g = [[] for _ in range(V + 1)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for u in range(V):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for v, w in adj[u]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        g[u].append((v, w + h[u] - h[v]))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for u in range(V):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    d = dijkstra(u, V, g)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    row = []&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for v in range(V):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        dist = -1 if d[v] == INF else int(d[v] - h[u] + h[v])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        row.append(str(dist))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    print(&apos; &apos;.join(row))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;main()&lt;code&gt;,     },   ]}   cases={[     {       label: &quot;3정점 음수 간선 그래프&quot;,       input: &lt;/code&gt;3 4
0 1 -1
0 2 4
1 2 3
2 1 -2&lt;code&gt;,       output: &lt;/code&gt;0 -1 2
-1 0 3
-1 -2 0&lt;code&gt;,     },     {       label: &quot;음수 사이클 탐지&quot;,       input: &lt;/code&gt;3 3
0 1 1
1 2 -3
2 0 1&lt;code&gt;,       output: &lt;/code&gt;음수 사이클 존재`,
},
]}
/&gt;&lt;/p&gt;
&lt;h2 id=&quot;복잡도&quot;&gt;복잡도&lt;/h2&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;방법&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;복잡도&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;음수 간선&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;적합한 그래프&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Floyd-Warshall&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(V³)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;허용&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Dense&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Johnson&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(VE log V)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;허용&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Sparse&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Dijkstra × V&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(V(E+V) log V)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;불가&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Sparse&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Sparse (E = O(V)) 일 때: Johnson O(V² log V) vs Floyd-Warshall O(V³).
Dense (E = O(V²)) 일 때: Johnson O(V³ log V) &gt; Floyd-Warshall O(V³). Dense에는 Floyd-Warshall 사용.&lt;/p&gt;
&lt;h2 id=&quot;함정&quot;&gt;함정&lt;/h2&gt;
&lt;h3 id=&quot;1-음수-사이클-미탐지&quot;&gt;1. 음수 사이클 미탐지&lt;/h3&gt;
&lt;p&gt;Bellman-Ford 단계에서 음수 사이클을 탐지하지 않으면 잘못된 결과. V번 완화 후 한 번 더 완화 시도로 탐지.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
음수 사이클이 있으면 최단 경로가 정의되지 않음. 반드시 탐지 후 처리.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;2-임시-정점-인덱스-충돌&quot;&gt;2. 임시 정점 인덱스 충돌&lt;/h3&gt;
&lt;p&gt;기존 정점 0..V-1에 임시 정점 V를 추가. 배열 크기를 V+1로 할당.&lt;/p&gt;
&lt;h3 id=&quot;3-거리-복원-공식-오류&quot;&gt;3. 거리 복원 공식 오류&lt;/h3&gt;
&lt;p&gt;재가중 거리 d’[u][v]에서 원래 거리 복원: &lt;code&gt;d[u][v] = d&apos;[u][v] - h[u] + h[v]&lt;/code&gt;. 부호 혼동 주의.&lt;/p&gt;
&lt;h3 id=&quot;4-dense-graph에서-비효율&quot;&gt;4. Dense graph에서 비효율&lt;/h3&gt;
&lt;p&gt;E = O(V²)이면 Johnson O(V³ log V)로 Floyd-Warshall O(V³)보다 느림. Dense graph에는 Floyd-Warshall 사용.&lt;/p&gt;
&lt;h3 id=&quot;5-정수-오버플로&quot;&gt;5. 정수 오버플로&lt;/h3&gt;
&lt;p&gt;h[u] - h[v]가 음수일 수 있으므로 &lt;code&gt;long long&lt;/code&gt; 사용. 재가중 후 w’가 음수가 되면 안 됨.&lt;/p&gt;
&lt;h2 id=&quot;boj-연습-문제&quot;&gt;BOJ 연습 문제&lt;/h2&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;번호&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;제목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;관련 개념&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 11404&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;플로이드&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Floyd-Warshall (비교)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1865&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;웜홀&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Bellman-Ford (음수 사이클)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1753&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;최단경로&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Dijkstra (기반 알고리즘)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 11657&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;타임머신&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Bellman-Ford SSSP&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[floyd-warshall|Floyd-Warshall]]&lt;/li&gt;
&lt;li&gt;[[dijkstra|Dijkstra]]&lt;/li&gt;
&lt;li&gt;[[bellman-ford|Bellman-Ford]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>algorithm</category><category>graph</category><category>shortest-path</category><category>apsp</category><author>koa (김신건)</author></item><item><title>Markov Chain: 상태 전이 확률</title><link>https://shinkeonkim.com/wiki/algorithm/foundation/markov-chain/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/algorithm/foundation/markov-chain/</guid><pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Markov Chain&lt;/strong&gt; 은 &lt;em&gt;다음 상태가 오직 현재 상태에만 의존&lt;/em&gt;하는 확률 과정. &lt;strong&gt;Memoryless property (Markov property)&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;$$
P(X_{t+1} = j \mid X_0, X_1, \ldots, X_t) = P(X_{t+1} = j \mid X_t)
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;전이 행렬 (Transition Matrix)&lt;/strong&gt; $P$: $P_{ij} = P(\text{다음} = j \mid \text{현재} = i)$.
각 행의 합 = 1 (확률 보존).&lt;/p&gt;
&lt;h2 id=&quot;문제-상황과-동기&quot;&gt;문제 상황과 동기&lt;/h2&gt;
&lt;p&gt;전형적인 PS 문제 패턴:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;“N 번 이동 후 위치 j 에 있을 확률” → 전이 행렬 거듭제곱&lt;/li&gt;
&lt;li&gt;“장기적으로 각 상태에 있는 비율” → 정류 분포&lt;/li&gt;
&lt;li&gt;“이 게임에서 파산할 확률” → Gambler’s ruin&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;시각화&quot;&gt;시각화&lt;/h2&gt;
&lt;h3 id=&quot;날씨-예측-마르코프-체인&quot;&gt;날씨 예측 마르코프 체인&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;stateDiagram-v2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Sunny --&gt; Sunny : 0.8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Sunny --&gt; Cloudy : 0.2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Cloudy --&gt; Sunny : 0.4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Cloudy --&gt; Cloudy : 0.6&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sunny = 맑음, Cloudy = 흐림. 전이 행렬:&lt;/p&gt;
&lt;p&gt;$$
P = \begin{pmatrix} 0.8 &amp;#x26; 0.2 \ 0.4 &amp;#x26; 0.6 \end{pmatrix}
$$&lt;/p&gt;
&lt;h3 id=&quot;n-step-전이-흐름&quot;&gt;N-step 전이 흐름&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    V0[&quot;초기 분포 v0&quot;] --&gt;|&quot;곱하기 P&quot;| V1[&quot;1-step 분포 v1&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    V1 --&gt;|&quot;곱하기 P&quot;| V2[&quot;2-step 분포 v2&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    V2 --&gt;|&quot;...&quot;| Vn[&quot;n-step: v0 * P의 n제곱&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;$v_0 \cdot P^n$ 의 j 번째 원소 = 초기 분포 $v_0$ 에서 출발해 n 스텝 후 상태 j 에 있을 확률.&lt;/p&gt;
&lt;h2 id=&quot;핵심-아이디어&quot;&gt;핵심 아이디어&lt;/h2&gt;
&lt;h3 id=&quot;행렬-거듭제곱-n-step-전이&quot;&gt;행렬 거듭제곱 (N-step 전이)&lt;/h3&gt;
&lt;p&gt;$P^n_{ij}$ = i 에서 j 로 n 스텝에 도달할 확률.&lt;/p&gt;
&lt;p&gt;[[matrix-exponentiation|Matrix Exponentiation]] 으로 O(K^3 log N) 에 계산. K = 상태 수.&lt;/p&gt;
&lt;h3 id=&quot;stationary-distribution&quot;&gt;Stationary Distribution&lt;/h3&gt;
&lt;p&gt;$\pi P = \pi$ 를 만족하는 확률벡터 $\pi$ (원소 합 = 1).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ergodic&lt;/strong&gt; 마르코프 체인 (irreducible + aperiodic) 이면 유일한 정류 분포 존재, 어떤 초기 분포에서도 수렴.&lt;/p&gt;
&lt;p&gt;풀이: $\pi (P - I) = 0$ 에서 마지막 방정식을 $\sum \pi_i = 1$ 로 교체 후 선형계 풀기.&lt;/p&gt;
&lt;h3 id=&quot;gamblers-ruin&quot;&gt;Gambler’s Ruin&lt;/h3&gt;
&lt;p&gt;가진 돈 i, 목표 N, 매 라운드 이길 확률 p:&lt;/p&gt;
&lt;p&gt;$$
q_i = p \cdot q_{i+1} + (1-p) \cdot q_{i-1}, \quad q_0 = 1,, q_N = 0
$$&lt;/p&gt;
&lt;p&gt;해 ($p \neq 0.5$):&lt;/p&gt;
&lt;p&gt;$$
q_i = \frac{(q/p)^N - (q/p)^i}{(q/p)^N - 1}
$$&lt;/p&gt;
&lt;p&gt;카지노에서 p = 49% 이더라도 목표가 크면 파산 확률이 압도적으로 높은 이유.&lt;/p&gt;
&lt;h2 id=&quot;알고리즘&quot;&gt;알고리즘&lt;/h2&gt;
&lt;h3 id=&quot;n-step-전이&quot;&gt;N-step 전이&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;P = K x K 전이 행렬&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Pn = matrix_power(P, n)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;answer = Pn[start][end]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;정류-분포-power-iteration&quot;&gt;정류 분포 (Power Iteration)&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;pi = [1/K, 1/K, ..., 1/K]  # 균등 초기&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for t in 1..T:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    pi = pi * P             # 행벡터 x 행렬&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# 수렴 후 pi = 정류 분포&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;수렴 속도 = 두 번째로 큰 고유값의 절댓값 (spectral gap).&lt;/p&gt;
&lt;h3 id=&quot;pagerank&quot;&gt;PageRank&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;각 웹 페이지 = 상태, 하이퍼링크 = 전이&lt;/li&gt;
&lt;li&gt;댐핑 인수 d = 0.85: $P’ = d \cdot P_{link} + (1-d) \cdot \mathbf{1}/K$&lt;/li&gt;
&lt;li&gt;Stationary distribution of P’ = PageRank score&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;구현&quot;&gt;구현&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
variants={[
{
language: “cpp”,
label: “C++”,
code: `// Markov Chain: 행렬 거듭제곱 + 정류 분포
#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;
using Matrix = vector&amp;#x3C;vector&lt;double&gt;&gt;;&lt;/double&gt;&lt;/p&gt;
&lt;p&gt;int K;&lt;/p&gt;
&lt;p&gt;Matrix mat_mul(const Matrix&amp;#x26; A, const Matrix&amp;#x26; B) {
Matrix C(K, vector&lt;double&gt;(K, 0));
for (int i = 0; i &amp;#x3C; K; i++)
for (int k = 0; k &amp;#x3C; K; k++)
for (int j = 0; j &amp;#x3C; K; j++)
C[i][j] += A[i][k] * B[k][j];
return C;
}&lt;/double&gt;&lt;/p&gt;
&lt;p&gt;Matrix mat_pow(Matrix M, long long n) {
Matrix R(K, vector&lt;double&gt;(K, 0));
for (int i = 0; i &amp;#x3C; K; i++) R[i][i] = 1.0;  // 단위 행렬
while (n &gt; 0) {
if (n &amp;#x26; 1) R = mat_mul(R, M);
M = mat_mul(M, M);
n &gt;&gt;= 1;
}
return R;
}&lt;/double&gt;&lt;/p&gt;
&lt;p&gt;int main() {
// 날씨: 0=맑음, 1=흐림
K = 2;
Matrix P = {{0.8, 0.2}, {0.4, 0.6}};&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// N-step 전이&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;long long n = 10;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Matrix Pn = mat_pow(P, n);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cout &amp;#x3C;&amp;#x3C; &quot;맑음 시작, &quot; &amp;#x3C;&amp;#x3C; n &amp;#x3C;&amp;#x3C; &quot;일 후:\\n&quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;printf(&quot;  맑음 확률: %.4f\\n&quot;, Pn[0][0]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;printf(&quot;  흐림 확률: %.4f\\n&quot;, Pn[0][1]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 정류 분포 (Power iteration)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;vector&amp;#x3C;double&gt; pi(K, 1.0 / K);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for (int t = 0; t &amp;#x3C; 1000; t++) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    vector&amp;#x3C;double&gt; npi(K, 0);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for (int i = 0; i &amp;#x3C; K; i++)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        for (int j = 0; j &amp;#x3C; K; j++)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            npi[j] += pi[i] * P[i][j];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    pi = npi;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;printf(&quot;정류 분포: 맑음=%.4f, 흐림=%.4f\\n&quot;, pi[0], pi[1]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 정류 분포: 맑음=0.6667, 흐림=0.3333&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 이론값: pi0 = 0.4/(0.2+0.4) = 2/3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;code&gt;,     },     {       language: &quot;python&quot;,       label: &quot;Python&quot;,       code: &lt;/code&gt;# Markov Chain: 전이 행렬 + 정류 분포
import numpy as np&lt;/p&gt;
&lt;h1 id=&quot;날씨-예측-0맑음-1흐림&quot;&gt;날씨 예측: 0=맑음, 1=흐림&lt;/h1&gt;
&lt;p&gt;P = np.array([[0.8, 0.2],
[0.4, 0.6]])&lt;/p&gt;
&lt;h1 id=&quot;n-step-전이-행렬-거듭제곱&quot;&gt;N-step 전이 (행렬 거듭제곱)&lt;/h1&gt;
&lt;p&gt;n = 10
Pn = np.linalg.matrix_power(P, n)
print(f”맑음 시작, {n}일 후:”)
print(f”  맑음 확률: {Pn[0][0]:.4f}”)
print(f”  흐림 확률: {Pn[0][1]:.4f}“)&lt;/p&gt;
&lt;h1 id=&quot;정류-분포-반복법&quot;&gt;정류 분포 (반복법)&lt;/h1&gt;
&lt;p&gt;pi = np.array([0.5, 0.5])
for _ in range(1000):
pi = pi @ P
print(f”정류 분포 (반복): 맑음={pi[0]:.4f}, 흐림={pi[1]:.4f}“)&lt;/p&gt;
&lt;h1 id=&quot;정류-분포-고유벡터법&quot;&gt;정류 분포 (고유벡터법)&lt;/h1&gt;
&lt;p&gt;eigenvalues, eigenvectors = np.linalg.eig(P.T)
idx = np.argmin(abs(eigenvalues - 1))
pi_exact = eigenvectors[:, idx].real
pi_exact /= pi_exact.sum()
print(f”정류 분포 (고유벡터): 맑음={pi_exact[0]:.4f}, 흐림={pi_exact[1]:.4f}“)&lt;/p&gt;
&lt;h1 id=&quot;gamblers-ruin-p049-초기-자금50-목표100&quot;&gt;Gambler’s Ruin: p=0.49, 초기 자금=50, 목표=100&lt;/h1&gt;
&lt;p&gt;def gambler_ruin(i, N, p):
if p == 0.5:
return 1 - i / N
r = (1 - p) / p
return (r&lt;strong&gt;N - r&lt;/strong&gt;i) / (r**N - 1)&lt;/p&gt;
&lt;p&gt;print(f”카지노 p=0.49, i=50, N=100: 파산 확률 = {gambler_ruin(50,100,0.49):.4f}”)`,
},
]}
cases={[
{
label: “10일 후 날씨 예측”,
input: “P=[[0.8,0.2],[0.4,0.6]], start=맑음, n=10”,
output: “맑음 확률: 0.6667, 흐림 확률: 0.3333”,
},
{
label: “정류 분포”,
input: “P=[[0.8,0.2],[0.4,0.6]]”,
output: “맑음=0.6667, 흐림=0.3333”,
},
{
label: “Gambler’s ruin”,
input: “p=0.49, i=50, N=100”,
output: “파산 확률 = 0.8824”,
},
]}
/&gt;&lt;/p&gt;
&lt;h2 id=&quot;복잡도&quot;&gt;복잡도&lt;/h2&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;항목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;값&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;N-step 계산&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(K^3 log N)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Power Iteration 수렴&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(K^2 x T)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;고유벡터법&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(K^3)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;공간&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(K^2)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;K = 상태 수. K 가 크면 희소 행렬 표현 + Power Iteration.&lt;/p&gt;
&lt;h2 id=&quot;응용&quot;&gt;응용&lt;/h2&gt;
&lt;h3 id=&quot;mcmc-markov-chain-monte-carlo&quot;&gt;MCMC (Markov Chain Monte Carlo)&lt;/h3&gt;
&lt;p&gt;복잡한 분포에서 직접 샘플링 불가능할 때, 해당 분포를 정류 분포로 갖는 마르코프 체인 구성 후 충분히 진행해 샘플 획득.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Metropolis-Hastings&lt;/strong&gt;: 제안 분포 q, 수용 확률 $\min(1, p(x’)/p(x))$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gibbs Sampling&lt;/strong&gt;: 각 변수를 조건부 분포에서 순서대로 샘플링&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;랜덤-워크&quot;&gt;랜덤 워크&lt;/h3&gt;
&lt;p&gt;무방향 그래프에서 각 정점의 방문 비율 = degree / (2E). 정류 분포로 분석.&lt;/p&gt;
&lt;h2 id=&quot;함정&quot;&gt;함정&lt;/h2&gt;
&lt;h3 id=&quot;1-전이-행렬-방향&quot;&gt;1. 전이 행렬 방향&lt;/h3&gt;
&lt;p&gt;행 합 = 1 (행이 현재 상태). 열 합 = 1 로 착각하면 전치 행렬 사용 오류.&lt;/p&gt;
&lt;h3 id=&quot;2-reducible-vs-irreducible&quot;&gt;2. Reducible vs Irreducible&lt;/h3&gt;
&lt;p&gt;일부 상태 간 도달 불가능하면 reducible. 정류 분포가 유일하지 않을 수 있음.&lt;/p&gt;
&lt;h3 id=&quot;3-periodic-체인&quot;&gt;3. Periodic 체인&lt;/h3&gt;
&lt;p&gt;주기 d &gt; 1 이면 수렴 안 하고 진동. 자가 루프 추가로 aperiodic 만들기 가능.&lt;/p&gt;
&lt;h3 id=&quot;4-부동소수점-누적-오차&quot;&gt;4. 부동소수점 누적 오차&lt;/h3&gt;
&lt;p&gt;행렬 거듭제곱 반복 시 확률 합이 1 에서 벗어남. 주기적으로 정규화하거나 분수 표현 사용.&lt;/p&gt;
&lt;h3 id=&quot;5-ps-에서-정수형-행렬&quot;&gt;5. PS 에서 정수형 행렬&lt;/h3&gt;
&lt;p&gt;확률 문제를 분수/유리수로 처리하면 정밀도 보장. 모듈러 산술과 결합 가능.&lt;/p&gt;
&lt;h2 id=&quot;boj-연습-문제&quot;&gt;BOJ 연습 문제&lt;/h2&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;번호&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;제목&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;정답률&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;링크&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 10830&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;행렬 제곱&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;47.3%&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/kokoa-lab/boj-problems/tree/main/organize_problems/10800-10899/10830&quot;&gt;kokoa-lab&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 11444&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;피보나치 수 6&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;51.2%&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/kokoa-lab/boj-problems/tree/main/organize_problems/11400-11499/11444&quot;&gt;kokoa-lab&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 2749&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;피보나치 수 3&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;50.0%&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/kokoa-lab/boj-problems/tree/main/organize_problems/2700-2799/2749&quot;&gt;kokoa-lab&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 13252&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;인터넷&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;32.1%&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/kokoa-lab/boj-problems/tree/main/organize_problems/13200-13299/13252&quot;&gt;kokoa-lab&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[matrix-exponentiation|Matrix Exponentiation]]&lt;/li&gt;
&lt;li&gt;[[expected-value|Expected Value]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>algorithm</category><category>foundation</category><category>probability</category><category>markov</category><author>koa (김신건)</author></item><item><title>Matrix Exponentiation: 선형 점화식 fast</title><link>https://shinkeonkim.com/wiki/algorithm/dp/matrix-exponentiation/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/algorithm/dp/matrix-exponentiation/</guid><pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;선형 점화식을 행렬 곱으로 표현한 뒤, &lt;strong&gt;행렬 거듭제곱을 log 시간에&lt;/strong&gt; 계산하여 N 번째 항을 O(K³ log N) 에 얻는 기법. K = 상태 차원.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;시간&lt;/strong&gt;: O(K³ log N). K 작을 때 효율적&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;핵심&lt;/strong&gt;: 반복 제곱법 (binary exponentiation)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;문제-상황&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p&gt;점화식의 N 번째 항을 아주 큰 N (≥ 10^18) 에 대해 구해야 할 때.&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;방법&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;시간&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;N 범위&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;직접 반복&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(K²·N)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;N ≤ 10^7&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Matrix Exponentiation&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(K³ log N)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;N ≤ 10^18&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Berlekamp-Massey + Kitamasa&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(K² log N)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;N ≤ 10^18, K 클 때&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;K = 상태 차원 (피보나치 → K=2, k차 선형 점화식 → K=k).&lt;/p&gt;
&lt;h2 id=&quot;시각화&quot;&gt;시각화&lt;/h2&gt;
&lt;p&gt;이진 거듭제곱: A^13 = A^8 × A^4 × A^1 (13 = 1101₂).&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P[&quot;p = 13&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B0[&quot;p &amp;#x26; 1 = 1\nR *= A&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S0[&quot;A = A * A\n(A^2)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B1[&quot;p &amp;#x26; 1 = 0&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S1[&quot;A = A * A\n(A^4)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B2[&quot;p &amp;#x26; 1 = 1\nR *= A^4&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S2[&quot;A = A * A\n(A^8)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B3[&quot;p &amp;#x26; 1 = 1\nR *= A^8&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Done[&quot;R = A^1 * A^4 * A^8\n= A^13&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    P --&gt; B0 --&gt; S0 --&gt; B1 --&gt; S1 --&gt; B2 --&gt; S2 --&gt; B3 --&gt; Done&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;각 비트마다 A 를 제곱, 비트가 1 이면 결과에 곱함. 행렬도 동일하게 적용.&lt;/p&gt;
&lt;h2 id=&quot;핵심-아이디어&quot;&gt;핵심 아이디어&lt;/h2&gt;
&lt;p&gt;K 차 선형 점화식을 행렬 형태로:&lt;/p&gt;
&lt;p&gt;$$
\begin{pmatrix} a_{n+1} \ a_n \ \vdots \ a_{n-k+2} \end{pmatrix}
= M^n
\begin{pmatrix} a_k \ a_{k-1} \ \vdots \ a_1 \end{pmatrix}
$$&lt;/p&gt;
&lt;p&gt;전이 행렬 M 의 n 제곱을 binary exponentiation 으로 계산 → O(K³ log N).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;모듈러 산술&lt;/strong&gt;: 결과가 MOD 로 나눈 나머지인 경우, 각 곱셈 후 &lt;code&gt;% MOD&lt;/code&gt; 적용.&lt;/p&gt;
&lt;h2 id=&quot;fibonacci-예&quot;&gt;Fibonacci 예&lt;/h2&gt;
&lt;p&gt;$$
\begin{pmatrix} F_{n+1} \ F_n \end{pmatrix}
= \begin{pmatrix} 1 &amp;#x26; 1 \ 1 &amp;#x26; 0 \end{pmatrix}^n
\begin{pmatrix} F_1 \ F_0 \end{pmatrix}
$$&lt;/p&gt;
&lt;p&gt;K = 2, 전이 행렬 = 2×2. N = 10^18 도 O(8 log N) = O(240) 번 곱셈.&lt;/p&gt;
&lt;h2 id=&quot;알고리즘&quot;&gt;알고리즘&lt;/h2&gt;
&lt;h3 id=&quot;행렬-곱&quot;&gt;행렬 곱&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;mul(A, B):  // K×K 행렬 곱&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    C = K×K 영행렬&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for i in 0..K:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        for k in 0..K:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            if A[i][k] == 0: continue  // 스킵 최적화&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            for j in 0..K:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                C[i][j] += A[i][k] * B[k][j]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                C[i][j] %= MOD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return C&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;반복-제곱법&quot;&gt;반복 제곱법&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;pow(A, p):  // A^p 계산&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    R = K×K 단위 행렬&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    while p &gt; 0:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        if p &amp;#x26; 1: R = mul(R, A)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        A = mul(A, A)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        p &gt;&gt;= 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return R&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;일반-k차-선형-점화식&quot;&gt;일반 K차 선형 점화식&lt;/h3&gt;
&lt;p&gt;점화식 &lt;code&gt;a[n] = c1*a[n-1] + c2*a[n-2] + ... + ck*a[n-k]&lt;/code&gt; 의 전이 행렬:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;M = [c1  c2  ...  c_{k-1}  ck ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    [1   0   ...  0        0  ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    [0   1   ...  0        0  ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    [         ...             ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    [0   0   ...  1        0  ]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;상태 벡터 &lt;code&gt;[a[n], a[n-1], ..., a[n-k+1]]&lt;/code&gt; 에 M 을 곱하면 &lt;code&gt;[a[n+1], ..., a[n-k+2]]&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;구현&quot;&gt;구현&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
language=“cpp”
label=“C++ (행렬 거듭제곱)”
outputLanguage=“text”
outputLabel=“결과”
title=“N번째 피보나치 수 (mod 1e9+7)”
code={`#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;&lt;/p&gt;
&lt;p&gt;const long long MOD = 1e9 + 7;
const int K = 2;
using Mat = array&amp;#x3C;array&amp;#x3C;long long, K&gt;, K&gt;;&lt;/p&gt;
&lt;p&gt;Mat mul(const Mat&amp;#x26; A, const Mat&amp;#x26; B) {
Mat C{};
for (int i = 0; i &amp;#x3C; K; i++)
for (int k = 0; k &amp;#x3C; K; k++) {
if (!A[i][k]) continue;
for (int j = 0; j &amp;#x3C; K; j++)
C[i][j] = (C[i][j] + A[i][k] * B[k][j]) % MOD;
}
return C;
}&lt;/p&gt;
&lt;p&gt;Mat matpow(Mat A, long long p) {
Mat R{};
for (int i = 0; i &amp;#x3C; K; i++) R[i][i] = 1; // 단위 행렬
while (p &gt; 0) {
if (p &amp;#x26; 1) R = mul(R, A);
A = mul(A, A);
p &gt;&gt;= 1;
}
return R;
}&lt;/p&gt;
&lt;p&gt;long long fib(long long n) {
if (n &amp;#x3C;= 0) return 0;
if (n == 1) return 1;
// [ F(n+1) ]   [ 1 1 ]^n   [ 1 ]
// [ F(n)   ] = [ 1 0 ]   * [ 0 ]
Mat M = {{{1, 1}, {1, 0}}};
Mat R = matpow(M, n - 1);
// R * [F(1), F(0)] = R * [1, 0]
return R[0][0]; // F(n)
}&lt;/p&gt;
&lt;p&gt;int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
long long n;
cin &gt;&gt; n;
cout &amp;#x3C;&amp;#x3C; fib(n) &amp;#x3C;&amp;#x3C; “\n”;
// 검증: fib(10) = 55
for (int i = 1; i &amp;#x3C;= 10; i++)
cout &amp;#x3C;&amp;#x3C; “F(” &amp;#x3C;&amp;#x3C; i &amp;#x3C;&amp;#x3C; ”) = ” &amp;#x3C;&amp;#x3C; fib(i) &amp;#x3C;&amp;#x3C; “\n”;
return 0;
}&lt;code&gt;}   output={&lt;/code&gt;// 입력: 10
55
F(1) = 1
F(2) = 1
F(3) = 2
F(4) = 3
F(5) = 5
F(6) = 8
F(7) = 13
F(8) = 21
F(9) = 34
F(10) = 55`}
/&gt;&lt;/p&gt;
&lt;h2 id=&quot;응용&quot;&gt;응용&lt;/h2&gt;
&lt;h3 id=&quot;그래프-경로-수&quot;&gt;그래프 경로 수&lt;/h3&gt;
&lt;p&gt;인접 행렬 A 에서 &lt;code&gt;A^k[u][v]&lt;/code&gt; = 정점 u 에서 v 까지 길이 정확히 k 인 경로의 수.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 길이 k 경로 수&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Mat A &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; /* 인접 행렬 */&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Mat Ak &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; matpow&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(A, k);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;cout &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Ak[u][v] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; MOD &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;dfa-상태-전이&quot;&gt;DFA 상태 전이&lt;/h3&gt;
&lt;p&gt;문자열 자동자에서 상태 전이 행렬 = 알파벳 크기 × 상태 수. 길이 N 의 수락 문자열 개수를 O(|states|³ log N) 에 계산.&lt;/p&gt;
&lt;h3 id=&quot;tribonacci-등-고차-점화식&quot;&gt;Tribonacci 등 고차 점화식&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// a[n] = a[n-1] + a[n-2] + a[n-3]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 전이 행렬 3x3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Mat M &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {{{&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;},{&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;},{&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}}};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;복잡도&quot;&gt;복잡도&lt;/h2&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;연산&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;시간&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;메모리&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;행렬 곱 1회&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(K³)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(K²)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;행렬 거듭제곱&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(K³ log N)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(K²)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Kitamasa 기법&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(K² log N)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(K)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;K 가 작을수록 실용적. K ≥ 100 이면 Kitamasa 또는 Berlekamp-Massey 고려.&lt;/p&gt;
&lt;h2 id=&quot;함정&quot;&gt;함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
&lt;strong&gt;K 에 따른 시간 복잡도 과소 평가&lt;/strong&gt;: K = 10 이면 1회 곱셈이 O(1000). N = 10^9, K = 50 이면 O(50³ × 30) = O(375만) 으로 충분하지만, K = 200 이면 O(200³ × 30) = O(240만×1000) → TLE.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
&lt;strong&gt;단위 행렬 초기화 누락&lt;/strong&gt;: &lt;code&gt;matpow&lt;/code&gt; 에서 R 을 단위 행렬로 초기화하지 않으면 A^0 = 영행렬 → 모든 결과 0.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
&lt;strong&gt;MOD 없이 long long 오버플로&lt;/strong&gt;: K×K 행렬 원소가 최대 MOD-1 ≈ 10^9, 두 원소 곱이 10^18 → long long 최대값 내. 하지만 K 번 누적하면 K × (MOD-1)² → K = 1000 이상이면 __int128 또는 중간 MOD 필요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;N = 0 처리: 관례상 F(0) = 0, F(1) = 1. 문제마다 정의 확인.&lt;/li&gt;
&lt;li&gt;비선형 점화식 (예: &lt;code&gt;a[n] = a[n-1]^2&lt;/code&gt;) 은 행렬 방법 미적용.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;boj&quot;&gt;BOJ&lt;/h2&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;문제&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;설명&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/11444&quot;&gt;BOJ 11444 피보나치 수 6&lt;/a&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;행렬 거듭제곱 기본, N ≤ 10^18&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/12850&quot;&gt;BOJ 12850 본대 산책 2&lt;/a&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;인접 행렬 D^(K), 경로 수&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/13430&quot;&gt;BOJ 13430 합 구하기&lt;/a&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;상태 확장&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/2749&quot;&gt;BOJ 2749 피보나치 수 3&lt;/a&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Pisano period 대안 vs 행렬&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/15991&quot;&gt;BOJ 15991 링클링크&lt;/a&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;DFA 행렬 거듭제곱&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[polynomial-division-kitamasa|Kitamasa]] (K차 점화식 O(K² log N))&lt;/li&gt;
&lt;li&gt;[[linear-algebra|Linear Algebra]] (행렬 기초)&lt;/li&gt;
&lt;li&gt;[[dp|DP 기초]]&lt;/li&gt;
&lt;li&gt;[[modular-arithmetic|모듈러 산술]] (MOD 연산)&lt;/li&gt;
&lt;li&gt;[[berlekamp-massey|Berlekamp-Massey]] (점화식 자동 탐지)&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>algorithm</category><category>dp</category><category>matrix</category><category>linear-recurrence</category><author>koa (김신건)</author></item><item><title>Median of Stream: 두 힙으로 중앙값 유지</title><link>https://shinkeonkim.com/wiki/algorithm/data-structure/median-of-stream/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/algorithm/data-structure/median-of-stream/</guid><pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;스트림으로 들어오는 정수의 &lt;strong&gt;중앙값을 O(log N)&lt;/strong&gt; 에 유지하는 문제.&lt;/p&gt;
&lt;p&gt;중앙값 = 정렬했을 때 가운데 값. 원소가 짝수 개이면 두 중간 값의 평균.&lt;/p&gt;
&lt;h2 id=&quot;문제-상황&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p&gt;정수가 하나씩 들어올 때마다 현재까지의 중앙값을 출력해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;naive&lt;/strong&gt;: 매번 정렬 후 중간 인덱스 반환. O(N log N) per query, 총 O(N^2 log N). N = 10^5이면 불가능.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;핵심 통찰&lt;/strong&gt;: 정렬된 배열을 절반으로 나눠, 아래 절반은 Max-heap, 위 절반은 Min-heap으로 유지. 두 heap의 top을 보면 O(1)로 중앙값을 알 수 있고, 삽입/삭제는 O(log N).&lt;/p&gt;
&lt;h2 id=&quot;시각화&quot;&gt;시각화&lt;/h2&gt;
&lt;h3 id=&quot;두-힙-구조&quot;&gt;두 힙 구조&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph L[&quot;L: Max-Heap (아래 절반)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        L1[&quot;...&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        L2[&quot;L.top (최댓값)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph R[&quot;R: Min-Heap (위 절반)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        R1[&quot;R.top (최솟값)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        R2[&quot;...&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    M[&quot;중앙값&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    L2 --&gt;|&quot;L.top &amp;#x3C;= R.top 보장&quot;| M&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    R1 --&gt;|&quot;L.top &amp;#x3C;= R.top 보장&quot;| M&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;원소-삽입-흐름-x-삽입&quot;&gt;원소 삽입 흐름 (x 삽입)&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A[&quot;x 입력&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B{&quot;x &amp;#x3C;= L.top ?&quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C[&quot;L에 삽입&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    D[&quot;R에 삽입&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    E{&quot;L.size &gt; R.size + 1 ?&quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    F{&quot;R.size &gt; L.size ?&quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    G[&quot;R에 L.top 이동&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    H[&quot;L에 R.top 이동&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    I[&quot;중앙값 반환&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A --&gt; B&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B --&gt;|&quot;YES&quot;| C&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B --&gt;|&quot;NO&quot;| D&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C --&gt; E&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    D --&gt; E&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    E --&gt;|&quot;YES&quot;| G&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    E --&gt;|&quot;NO&quot;| F&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    F --&gt;|&quot;YES&quot;| H&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    F --&gt;|&quot;NO&quot;| I&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    G --&gt; I&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    H --&gt; I&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;핵심-아이디어&quot;&gt;핵심 아이디어&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Max-heap &lt;code&gt;L&lt;/code&gt;&lt;/strong&gt;: 아래 절반 저장. top = 아래 절반의 최댓값.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Min-heap &lt;code&gt;R&lt;/code&gt;&lt;/strong&gt;: 위 절반 저장. top = 위 절반의 최솟값.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;불변식&lt;/strong&gt;: &lt;code&gt;L.top &amp;#x3C;= R.top&lt;/code&gt; (아래 절반 최댓값 &amp;#x3C;= 위 절반 최솟값)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;크기 균형&lt;/strong&gt;: &lt;code&gt;|L.size - R.size| &amp;#x3C;= 1&lt;/code&gt; (원소 수 차이 최대 1)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;중앙값 반환&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;L.size &gt; R.size&lt;/code&gt;: 중앙값 = &lt;code&gt;L.top&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;L.size == R.size&lt;/code&gt;: 중앙값 = &lt;code&gt;(L.top + R.top) / 2.0&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;삽입 시 균형 유지&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;x &amp;#x3C;= L.top이면 L에, 그렇지 않으면 R에 삽입&lt;/li&gt;
&lt;li&gt;L.size가 R.size + 1보다 크면: L.top을 R로 이동&lt;/li&gt;
&lt;li&gt;R.size가 L.size보다 크면: R.top을 L로 이동&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;알고리즘&quot;&gt;알고리즘&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;L: max-heap (아래 절반)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;R: min-heap (위 절반)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;add(x):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if L.empty() or x &amp;#x3C;= L.top():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        L.push(x)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    else:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        R.push(x)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    # 크기 균형 유지&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if L.size() &gt; R.size() + 1:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        R.push(L.top()); L.pop()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if R.size() &gt; L.size():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        L.push(R.top()); R.pop()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;median():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if L.size() &gt; R.size():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        return L.top()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return (L.top() + R.top()) / 2.0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;구현&quot;&gt;구현&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
variants={[
{
language: “cpp”,
label: “C++”,
code: `#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;&lt;/p&gt;
&lt;p&gt;int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;int n;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cin &gt;&gt; n;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;priority_queue&amp;#x3C;int&gt; L;                             // max-heap (아래 절반)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;priority_queue&amp;#x3C;int, vector&amp;#x3C;int&gt;, greater&amp;#x3C;int&gt;&gt; R;  // min-heap (위 절반)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;while (n--) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    int x;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    cin &gt;&gt; x;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // 삽입&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if (L.empty() || x &amp;#x3C;= L.top()) L.push(x);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    else R.push(x);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // 크기 균형 유지&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if ((int)L.size() &gt; (int)R.size() + 1) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        R.push(L.top()); L.pop();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if ((int)R.size() &gt; (int)L.size()) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        L.push(R.top()); R.pop();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // 중앙값 출력&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if (L.size() &gt; R.size()) cout &amp;#x3C;&amp;#x3C; L.top() &amp;#x3C;&amp;#x3C; &quot;\\n&quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    else cout &amp;#x3C;&amp;#x3C; (long long)(L.top() + R.top()) / 2 &amp;#x3C;&amp;#x3C; &quot;\\n&quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;return 0;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;code&gt;,     },     {       language: &quot;python&quot;,       label: &quot;Python&quot;,       code: &lt;/code&gt;import heapq
import sys
input = sys.stdin.readline&lt;/p&gt;
&lt;p&gt;def solve():
n = int(input())&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;L = []   # max-heap (음수화)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;R = []   # min-heap&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for _ in range(n):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    x = int(input())&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    # 삽입 (Python heapq는 min-heap, max-heap은 음수화)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if not L or x &amp;#x3C;= -L[0]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        heapq.heappush(L, -x)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    else:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        heapq.heappush(R, x)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    # 크기 균형 유지&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if len(L) &gt; len(R) + 1:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        heapq.heappush(R, -heapq.heappop(L))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if len(R) &gt; len(L):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        heapq.heappush(L, -heapq.heappop(R))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    # 중앙값 출력&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if len(L) &gt; len(R):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        print(-L[0])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    else:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        print((-L[0] + R[0]) // 2)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;solve()&lt;code&gt;,     },   ]}   cases={[     {       label: &quot;BOJ 1655 스타일&quot;,       input: &lt;/code&gt;7
1
5
2
10
-99
7
5&lt;code&gt;,       output: &lt;/code&gt;1
2
2
3
2
3
5&lt;code&gt;,     },     {       label: &quot;단조 증가&quot;,       input: &lt;/code&gt;4
1
2
3
4&lt;code&gt;,       output: &lt;/code&gt;1
1
2
2`,
},
]}
/&gt;&lt;/p&gt;
&lt;h2 id=&quot;복잡도&quot;&gt;복잡도&lt;/h2&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;항목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;값&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;삽입 add(x)&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(log N)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;중앙값 쿼리 median()&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(1)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;공간&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(N)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;N개 삽입 + N개 쿼리 합계&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(N log N)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;두 힙의 크기 합 = N. 삽입/삭제는 힙 연산이므로 O(log N).&lt;/p&gt;
&lt;h2 id=&quot;변형&quot;&gt;변형&lt;/h2&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;변형&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;방법&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;K번째 작은 수 유지&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;L.size = K, R.size = N-K 로 비율 조정&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;슬라이딩 윈도우 중앙값&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;힙 + lazy deletion. 삭제된 원소를 set으로 추적&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;온라인 중앙값 (정수 범위 제한)&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;세그먼트 트리 또는 BIT로 O(log V)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;함정&quot;&gt;함정&lt;/h2&gt;
&lt;h3 id=&quot;1-python에서-max-heap&quot;&gt;1. Python에서 max-heap&lt;/h3&gt;
&lt;p&gt;Python &lt;code&gt;heapq&lt;/code&gt;는 min-heap만 지원. Max-heap 구현 시 &lt;strong&gt;음수화&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;heapq.heappush(L, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;x)   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 삽입&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;L[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]                    &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# top 참조 (음수 되돌리기)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;heapq.heappop(L)        &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# pop (음수 되돌리기)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2-크기-균형-순서&quot;&gt;2. 크기 균형 순서&lt;/h3&gt;
&lt;p&gt;삽입 후 L의 크기 체크를 먼저, R의 크기 체크를 나중에 해야 함. 두 조건이 동시에 참이 되는 경우 있음.&lt;/p&gt;
&lt;h3 id=&quot;3-짝수-개-중앙값&quot;&gt;3. 짝수 개 중앙값&lt;/h3&gt;
&lt;p&gt;정수 두 개의 평균이 소수점 이하가 될 수 있음. 문제에 따라 정수 나눗셈(&lt;code&gt;//&lt;/code&gt;) 또는 실수 나눗셈(&lt;code&gt;/&lt;/code&gt;) 선택.&lt;/p&gt;
&lt;h3 id=&quot;4-size-비교-시-타입-주의-c&quot;&gt;4. size() 비교 시 타입 주의 (C++)&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;L.size() &gt; R.size() + 1&lt;/code&gt;에서 &lt;code&gt;size()&lt;/code&gt;는 &lt;code&gt;size_t&lt;/code&gt;(unsigned). R.size()가 0일 때 &lt;code&gt;R.size() - 1&lt;/code&gt;은 언더플로우. &lt;code&gt;(int)&lt;/code&gt; 캐스팅 권장.&lt;/p&gt;
&lt;h3 id=&quot;5-슬라이딩-윈도우-변형&quot;&gt;5. 슬라이딩 윈도우 변형&lt;/h3&gt;
&lt;p&gt;윈도우에서 원소를 제거할 때 힙에서 직접 삭제 불가. &lt;strong&gt;Lazy deletion&lt;/strong&gt;: 삭제 대상을 별도 집합에 기록, top 참조 시 삭제 대상이면 pop 반복.&lt;/p&gt;
&lt;h2 id=&quot;boj-연습-문제&quot;&gt;BOJ 연습 문제&lt;/h2&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;번호&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;제목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;난이도&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;알고리즘&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1655&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;가운데를 말해요&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Gold 2&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;두 힙&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 2696&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;중앙값 구하기&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Gold 2&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;두 힙&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 13537&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;수열과 쿼리 1&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Platinum 5&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;머지 소트 트리&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[priority-queue-heap|Priority Queue / Heap]]&lt;/li&gt;
&lt;li&gt;[[top-k-selection|Top K 선택]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>algorithm</category><category>data-structure</category><category>heap</category><category>streaming</category><author>koa (김신건)</author></item><item><title>Nim Game: XOR 규칙</title><link>https://shinkeonkim.com/wiki/algorithm/game/nim-game/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/algorithm/game/nim-game/</guid><pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Nim&lt;/strong&gt; 은 여러 돌무더기에서 두 플레이어가 번갈아 한 무더기에서 돌을 임의 개수 가져가는 게임. 마지막 돌을 가져가는 쪽이 승 (normal play convention).&lt;/p&gt;
&lt;h2 id=&quot;문제-상황과-동기&quot;&gt;문제 상황과 동기&lt;/h2&gt;
&lt;p&gt;“N 개의 돌무더기, 두 플레이어, 최적 플레이 시 누가 이기나?” 형태의 게임 이론 문제.&lt;/p&gt;
&lt;p&gt;핵심 질문: 주어진 상태가 &lt;strong&gt;필승(N-position)&lt;/strong&gt; 인가 &lt;strong&gt;필패(P-position)&lt;/strong&gt; 인가?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;P-position (Previous player wins)&lt;/strong&gt;: 이전 플레이어가 이기는 자리. 현재 플레이어(선공) 입장에서 필패.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;N-position (Next player wins)&lt;/strong&gt;: 현재 플레이어(선공)가 이기는 자리. 필승.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;시각화&quot;&gt;시각화&lt;/h2&gt;
&lt;h3 id=&quot;boutons-theorem-xor-으로-판별&quot;&gt;Bouton’s Theorem: XOR 으로 판별&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S1[&quot;상태: heap = 3, 4, 5&quot;] --&gt; X1[&quot;XOR = 2, 0이 아님&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    X1 --&gt; N1[&quot;N-position: 선공 승&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S2[&quot;상태: heap = 1, 2, 3&quot;] --&gt; X2[&quot;XOR = 0&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    X2 --&gt; P2[&quot;P-position: 선공 패&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;게임-트리-소규모-예시&quot;&gt;게임 트리 소규모 예시&lt;/h3&gt;
&lt;p&gt;돌무더기 2개, 상태 (a, b) 에서 XOR = a XOR b:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Root[&quot;(2, 3) XOR=1 N-pos&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Root --&gt; C[&quot;(2, 2) XOR=0 P-pos (필승 이동)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Root --&gt; A[&quot;(1, 3) XOR=2 N-pos&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Root --&gt; B[&quot;(0, 3) XOR=3 N-pos&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Root --&gt; D[&quot;(2, 1) XOR=3 N-pos&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Root --&gt; E[&quot;(2, 0) XOR=2 N-pos&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C --&gt; C1[&quot;(1, 2) XOR=3 N-pos&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C --&gt; C2[&quot;(0, 2) XOR=2 N-pos&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    style C fill:#ccffcc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    style Root fill:#ffffcc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    style C1 fill:#ffffcc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    style C2 fill:#ffffcc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(2, 3) 에서 (2, 2) 로 이동하면 XOR = 0 (P-pos). 이후 상대방의 모든 이동은 XOR != 0 으로 만들며, 선공은 항상 XOR = 0 으로 되돌릴 수 있음.&lt;/p&gt;
&lt;h2 id=&quot;핵심-아이디어&quot;&gt;핵심 아이디어&lt;/h2&gt;
&lt;h3 id=&quot;boutons-theorem&quot;&gt;Bouton’s Theorem&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;P-position (필패) $\Leftrightarrow$ 모든 무더기의 XOR = 0&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;$$
\text{heap}_1 \oplus \text{heap}_2 \oplus \cdots \oplus \text{heap}_n = 0 \Leftrightarrow \text{P-position}
$$&lt;/p&gt;
&lt;h3 id=&quot;증명-스케치&quot;&gt;증명 스케치&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;기저&lt;/strong&gt;: 모든 무더기 = 0 은 P-position. XOR = 0. (성립)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;P에서 N으로&lt;/strong&gt;: XOR = 0 인 상태에서 한 무더기 $h_i$ 를 $h_i’$ 으로 줄이면 $h_i’ &amp;#x3C; h_i$, 전체 XOR = $h_i \oplus h_i’ \neq 0$. 즉 P에서의 모든 이동은 N으로. (성립)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;N에서 P로&lt;/strong&gt;: XOR = $x \neq 0$ 이면 $x$ 의 최상위 비트를 1 로 갖는 무더기 $h_i$ 가 반드시 존재. $h_i’ = h_i \oplus x$ 로 설정하면 $h_i’ &amp;#x3C; h_i$ (크기 감소), 전체 XOR = 0. N에서 P로 가는 이동이 항상 존재. (성립)&lt;/p&gt;
&lt;h3 id=&quot;최적-이동-계산&quot;&gt;최적 이동 계산&lt;/h3&gt;
&lt;p&gt;N-position 에서 XOR = 0 으로 만드는 이동:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;x = xor_sum&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for i in 0..n-1:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    new_size = heap[i] XOR x&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if new_size &amp;#x3C; heap[i]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        heap[i] = new_size  # 이 무더기를 new_size로 줄임&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        break&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;misere-nim&quot;&gt;Misere Nim&lt;/h3&gt;
&lt;p&gt;마지막 돌을 가져가는 쪽이 &lt;strong&gt;패&lt;/strong&gt; (normal play 반대):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;모든 무더기가 크기 1 이하이면: 1 인 무더기 수가 홀수 = P-position&lt;/li&gt;
&lt;li&gt;그 외: 일반 Nim 과 동일 (XOR = 0 이면 P-position)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;알고리즘&quot;&gt;알고리즘&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Normal Nim 승패 판별&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;xor_sum = heap[0] XOR heap[1] XOR ... XOR heap[n-1]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;if xor_sum == 0:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    P-position (선공 패)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;else:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    N-position (선공 승)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Misere Nim&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;all_small = all(h &amp;#x3C;= 1 for h in heaps)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;if all_small:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    P-position iff count(h == 1) % 2 == 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;else:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    P-position iff xor_sum == 0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;구현&quot;&gt;구현&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
variants={[
{
language: “cpp”,
label: “C++”,
code: `// Nim Game: 승패 판별 + 최적 이동
#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;&lt;/p&gt;
&lt;p&gt;bool nim_winner(vector&lt;int&gt;&amp;#x26; heaps) {
int x = 0;
for (int h : heaps) x ^= h;
return x != 0;
}&lt;/int&gt;&lt;/p&gt;
&lt;p&gt;// 최적 이동: (무더기 인덱스, 줄일 개수). N-position 에서만 유효.
pair&amp;#x3C;int,int&gt; nim_optimal(vector&lt;int&gt; heaps) {
int x = 0;
for (int h : heaps) x ^= h;
for (int i = 0; i &amp;#x3C; (int)heaps.size(); i++) {
int ns = heaps[i] ^ x;
if (ns &amp;#x3C; heaps[i]) return {i, heaps[i] - ns};
}
return {-1, -1};
}&lt;/int&gt;&lt;/p&gt;
&lt;p&gt;bool misere_winner(vector&lt;int&gt;&amp;#x26; heaps) {
bool all_small = true;
for (int h : heaps) if (h &gt; 1) { all_small = false; break; }
if (all_small) {
int ones = 0;
for (int h : heaps) ones += h;
return ones % 2 == 0;  // 홀수 개 1 = P-position = 선공 패
}
int x = 0;
for (int h : heaps) x ^= h;
return x != 0;
}&lt;/int&gt;&lt;/p&gt;
&lt;p&gt;int main() {
vector&lt;int&gt; h1 = {3, 4, 5};
cout &amp;#x3C;&amp;#x3C; “XOR: ” &amp;#x3C;&amp;#x3C; (3^4^5) &amp;#x3C;&amp;#x3C; “\n”;
cout &amp;#x3C;&amp;#x3C; (nim_winner(h1) ? “선공 승” : “선공 패”) &amp;#x3C;&amp;#x3C; “\n”;&lt;/int&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;auto [idx, rem] = nim_optimal(h1);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cout &amp;#x3C;&amp;#x3C; &quot;최적: 무더기 &quot; &amp;#x3C;&amp;#x3C; idx &amp;#x3C;&amp;#x3C; &quot; 에서 &quot; &amp;#x3C;&amp;#x3C; rem &amp;#x3C;&amp;#x3C; &quot;개 제거\\n&quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;vector&amp;#x3C;int&gt; h2 = {1, 2, 3};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cout &amp;#x3C;&amp;#x3C; &quot;XOR: &quot; &amp;#x3C;&amp;#x3C; (1^2^3) &amp;#x3C;&amp;#x3C; &quot;\\n&quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cout &amp;#x3C;&amp;#x3C; (nim_winner(h2) ? &quot;선공 승&quot; : &quot;선공 패&quot;) &amp;#x3C;&amp;#x3C; &quot;\\n&quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// Misere Nim&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;vector&amp;#x3C;int&gt; h3 = {1, 1, 1};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cout &amp;#x3C;&amp;#x3C; &quot;Misere (1,1,1): &quot; &amp;#x3C;&amp;#x3C; (misere_winner(h3) ? &quot;선공 승&quot; : &quot;선공 패&quot;) &amp;#x3C;&amp;#x3C; &quot;\\n&quot;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;code&gt;,     },     {       language: &quot;python&quot;,       label: &quot;Python&quot;,       code: &lt;/code&gt;# Nim Game
from functools import reduce
from operator import xor&lt;/p&gt;
&lt;p&gt;def nim_winner(heaps):
&quot;&quot;&quot;True = N-position (선공 승)&quot;&quot;&quot;
return reduce(xor, heaps, 0) != 0&lt;/p&gt;
&lt;p&gt;def nim_optimal(heaps):
&quot;&quot;&quot;XOR=0 으로 만드는 최적 이동 (인덱스, 줄일 개수)&quot;&quot;&quot;
xsum = reduce(xor, heaps, 0)
for i, h in enumerate(heaps):
ns = h ^ xsum
if ns &amp;#x3C; h:
return i, h - ns
return -1, -1&lt;/p&gt;
&lt;p&gt;def misere_winner(heaps):
&quot;&quot;&quot;Misere Nim 승패&quot;&quot;&quot;
all_small = all(h &amp;#x3C;= 1 for h in heaps)
if all_small:
return sum(heaps) % 2 == 0  # 홀수 개 1 = P-position = 선공 패
return reduce(xor, heaps, 0) != 0&lt;/p&gt;
&lt;h1 id=&quot;n-position-예시&quot;&gt;N-position 예시&lt;/h1&gt;
&lt;p&gt;heaps = [3, 4, 5]
print(f”XOR: {reduce(xor, heaps)}”)  # 2
print(“선공 승” if nim_winner(heaps) else “선공 패”)
idx, rem = nim_optimal(heaps)
print(f”최적: 무더기 {idx} 에서 {rem}개 제거”)&lt;/p&gt;
&lt;h1 id=&quot;p-position-예시&quot;&gt;P-position 예시&lt;/h1&gt;
&lt;p&gt;heaps2 = [1, 2, 3]
print(f”XOR: {reduce(xor, heaps2)}”)  # 0
print(“선공 승” if nim_winner(heaps2) else “선공 패”)&lt;/p&gt;
&lt;h1 id=&quot;misere-nim-1&quot;&gt;Misere Nim&lt;/h1&gt;
&lt;p&gt;print(“Misere (1,1,1):”, “선공 승” if misere_winner([1,1,1]) else “선공 패”)&lt;code&gt;,     },     {       language: &quot;java&quot;,       label: &quot;Java&quot;,       code: &lt;/code&gt;// Nim Game&lt;/p&gt;
&lt;p&gt;public class Main {
static boolean nimWinner(int[] heaps) {
int x = 0;
for (int h : heaps) x ^= h;
return x != 0;
}
static int[] nimOptimal(int[] heaps) {
int x = 0;
for (int h : heaps) x ^= h;
for (int i = 0; i &amp;#x3C; heaps.length; i++) {
int ns = heaps[i] ^ x;
if (ns &amp;#x3C; heaps[i]) return new int[]{i, heaps[i] - ns};
}
return new int[]{-1, -1};
}
public static void main(String[] args) {
int[] h1 = {3, 4, 5};
System.out.println(“선공 ” + (nimWinner(h1) ? “승” : “패”));
int[] move = nimOptimal(h1);
System.out.println(“최적: 무더기 ” + move[0] + ” 에서 ” + move[1] + “개 제거”);
int[] h2 = {1, 2, 3};
System.out.println(“선공 ” + (nimWinner(h2) ? “승” : “패”));
}
}`,
},
]}
cases={[
{
label: “N-position (선공 승)”,
input: “heaps = [3, 4, 5]”,
output: “XOR: 2\n선공 승\n최적: 무더기 2 에서 4개 제거”,
},
{
label: “P-position (선공 패)”,
input: “heaps = [1, 2, 3]”,
output: “XOR: 0\n선공 패”,
},
{
label: “Misere Nim (1,1,1)”,
input: “heaps = [1, 1, 1]”,
output: “Misere (1,1,1): 선공 패”,
},
]}
/&gt;&lt;/p&gt;
&lt;h2 id=&quot;복잡도&quot;&gt;복잡도&lt;/h2&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;항목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;값&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;승패 판별&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(N)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;최적 이동&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(N)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;공간&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;O(N)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;N = 무더기 수. 무더기 크기와 무관하게 O(N).&lt;/p&gt;
&lt;h2 id=&quot;sprague-grundy-확장&quot;&gt;Sprague-Grundy 확장&lt;/h2&gt;
&lt;p&gt;Nim 은 Sprague-Grundy 정리의 원형.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Grundy value (Nim-value)&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;$$
G(\text{pos}) = \text{mex}({G(\text{move}) \mid \text{move} \in \text{reachable}})
$$&lt;/p&gt;
&lt;p&gt;mex = Minimum EXcludant (0, 1, 2, … 중 없는 가장 작은 수).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Nim 에서 크기 k 인 무더기: G = k (자명)&lt;/li&gt;
&lt;li&gt;복합 게임에서 XOR 합: $G(\text{sum}) = G_1 \oplus G_2 \oplus \cdots$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;임의의 impartial game (완전 정보, 두 플레이어, 이동 집합 동일) 은 Nim 무더기로 등가 변환 가능.&lt;/p&gt;
&lt;h2 id=&quot;조합-게임-이론-용어&quot;&gt;조합 게임 이론 용어&lt;/h2&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;개념&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;설명&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Normal play&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;마지막 이동하면 승&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Misere play&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;마지막 이동하면 패&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Impartial game&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;두 플레이어 이동 집합 동일&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Partizan game&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;이동 집합 플레이어마다 다름 (바둑 등)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Sprague-Grundy&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Impartial game = Nim 등가&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;함정&quot;&gt;함정&lt;/h2&gt;
&lt;h3 id=&quot;1-무더기-크기-0-처리&quot;&gt;1. 무더기 크기 0 처리&lt;/h3&gt;
&lt;p&gt;XOR 에서 0 은 영향 없음. 모두 0 이면 P-position (게임 종료).&lt;/p&gt;
&lt;h3 id=&quot;2-misere-와-normal-play-혼동&quot;&gt;2. Misere 와 Normal play 혼동&lt;/h3&gt;
&lt;p&gt;대부분 PS 문제는 normal play. Misere 조건은 반드시 명시.&lt;/p&gt;
&lt;h3 id=&quot;3-무더기-개수-1-인-경우&quot;&gt;3. 무더기 개수 1 인 경우&lt;/h3&gt;
&lt;p&gt;heap = [k] 이면 XOR = k. k != 0 이면 N-position. 선공이 전부 가져가면 승.&lt;/p&gt;
&lt;h3 id=&quot;4-정확히-k-개-제한-nim&quot;&gt;4. “정확히 k 개” 제한 Nim&lt;/h3&gt;
&lt;p&gt;매 턴 최대 k 개만 가져갈 수 있으면 일반 Nim 과 다름. Grundy 값 = heap mod (k+1).&lt;/p&gt;
&lt;h3 id=&quot;5-병렬-게임-sum-of-games&quot;&gt;5. 병렬 게임 (sum of games)&lt;/h3&gt;
&lt;p&gt;여러 Nim 이 독립적으로 진행되면 XOR 합 사용 가능. 독립적이지 않으면 Sprague-Grundy 수동 계산.&lt;/p&gt;
&lt;h2 id=&quot;boj-연습-문제&quot;&gt;BOJ 연습 문제&lt;/h2&gt;









































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;번호&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;제목&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;정답률&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;링크&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 11694&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;님 게임&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;68.2%&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/kokoa-lab/boj-problems/tree/main/organize_problems/11600-11699/11694&quot;&gt;kokoa-lab&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 9655&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;돌 게임&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;75.1%&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/kokoa-lab/boj-problems/tree/main/organize_problems/9600-9699/9655&quot;&gt;kokoa-lab&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 9656&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;돌 게임 2&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;73.5%&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/kokoa-lab/boj-problems/tree/main/organize_problems/9600-9699/9656&quot;&gt;kokoa-lab&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 9657&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;돌 게임 3&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;63.4%&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/kokoa-lab/boj-problems/tree/main/organize_problems/9600-9699/9657&quot;&gt;kokoa-lab&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 11062&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;카드 게임&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;47.5%&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/kokoa-lab/boj-problems/tree/main/organize_problems/11000-11099/11062&quot;&gt;kokoa-lab&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[sprague-grundy-theorem|Sprague-Grundy]]&lt;/li&gt;
&lt;li&gt;[[game-theory|Game Theory]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>algorithm</category><category>game</category><category>sprague-grundy</category><author>koa (김신건)</author></item><item><title>Order Statistics Tree (OST): rank/select 지원 BST</title><link>https://shinkeonkim.com/wiki/algorithm/data-structure/order-statistics-tree/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/algorithm/data-structure/order-statistics-tree/</guid><pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Order Statistics Tree (OST)&lt;/strong&gt; 는 각 노드에 &lt;strong&gt;서브트리 크기 (size)&lt;/strong&gt; 를 추가로 저장한 균형 [[binary-search-tree|BST]]. 두 가지 새 연산을 O(log N) 에 지원한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;rank(x)&lt;/strong&gt;: x 보다 작은 원소 수 (= x 의 0-indexed 순위)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;select(k)&lt;/strong&gt;: k 번째 원소 (0-indexed)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;일반 BST 에 &lt;code&gt;size&lt;/code&gt; 필드만 추가하면 구현 가능하지만, 균형이 깨지면 O(N) 로 퇴화한다. 실전에서는 &lt;strong&gt;Treap&lt;/strong&gt;, &lt;strong&gt;AVL&lt;/strong&gt;, &lt;strong&gt;Red-Black Tree&lt;/strong&gt; 기반으로 구현한다.&lt;/p&gt;
&lt;h2 id=&quot;문제-상황&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p&gt;정수 집합 &lt;code&gt;{3, 7, 1, 9, 5}&lt;/code&gt; 에 대해 다음을 O(log N) 에 처리하고 싶다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rank(5)&lt;/code&gt; = 2 (5보다 작은 원소: 1, 3)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;select(3)&lt;/code&gt; = 7 (정렬 순서로 3번째: 1, 3, 5, 7, 9 에서 index 3)&lt;/li&gt;
&lt;li&gt;삽입/삭제도 O(log N)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;정렬 배열이면 이진 탐색으로 rank/select O(log N) 가능하지만, &lt;strong&gt;삽입/삭제가 O(N)&lt;/strong&gt; (shift). OST 는 세 연산 모두 O(log N).&lt;/p&gt;
&lt;h2 id=&quot;시각화&quot;&gt;시각화&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A[&quot;5 (size=5)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B[&quot;2 (size=2)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C[&quot;8 (size=2)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    D[&quot;1 (size=1)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    E[&quot;3 (size=1)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    F[&quot;7 (size=1)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    G[&quot;9 (size=1)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A --&gt; B&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A --&gt; C&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B --&gt; D&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B --&gt; E&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C --&gt; F&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C --&gt; G&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;size&lt;/code&gt; = 노드 자신 + 왼쪽 서브트리 크기 + 오른쪽 서브트리 크기.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;rank(5)&lt;/code&gt; 계산: 루트(5)에서 왼쪽 서브트리 크기 = 2. 즉 rank = 2.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;select(3)&lt;/code&gt;: 루트 왼쪽 크기 = 2. 3 &gt; 2 이므로 오른쪽 서브트리에서 index 0 을 찾음. 오른쪽 루트(8), 왼쪽 크기=1, index 0 == 1 이므로 왼쪽 자식 7 반환.&lt;/p&gt;
&lt;h2 id=&quot;핵심-아이디어&quot;&gt;핵심 아이디어&lt;/h2&gt;
&lt;h3 id=&quot;불변식&quot;&gt;불변식&lt;/h3&gt;
&lt;p&gt;모든 노드 &lt;code&gt;n&lt;/code&gt; 에 대해:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;n-&gt;size = (n-&gt;l ? n-&gt;l-&gt;size : 0) + (n-&gt;r ? n-&gt;r-&gt;size : 0) + 1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;삽입/삭제 시 조상 노드의 size 를 모두 갱신해야 한다. (재귀 리턴 시 갱신)&lt;/p&gt;
&lt;h3 id=&quot;selectk-로직&quot;&gt;select(k) 로직&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;left_size = n-&gt;l ? n-&gt;l-&gt;size : 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;if k == left_size:    return n  (현재 노드가 k번째)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;if k  &amp;#x3C; left_size:   return select(n-&gt;l, k)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;else:                return select(n-&gt;r, k - left_size - 1)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;rankx-로직&quot;&gt;rank(x) 로직&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;if x &amp;#x3C;= n-&gt;key:  return rank(n-&gt;l, x)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;else:            return (n-&gt;l ? n-&gt;l-&gt;size : 0) + 1 + rank(n-&gt;r, x)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;알고리즘&quot;&gt;알고리즘&lt;/h2&gt;
&lt;h3 id=&quot;핵심-구조-및-헬퍼&quot;&gt;핵심 구조 및 헬퍼&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Node&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; key, size;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Node &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;l, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;r;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    Node&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; k&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) : &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(k), &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;l&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;nullptr&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;nullptr&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; sz&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Node&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; n&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) { &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n-&gt;size &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; update&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Node&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; n&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (n) n-&gt;size &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; sz&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(n-&gt;l) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; sz&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(n-&gt;r) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;select--rank&quot;&gt;select / rank&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Node&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; select&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Node&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; r&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; k&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;r) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nullptr&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; lsz &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; sz&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(r-&gt;l);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (k &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; lsz) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (k &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; lsz) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; select&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(r-&gt;l, k);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; select&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(r-&gt;r, k &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; lsz &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; rank&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Node&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; r&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; key&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;r) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (key &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r-&gt;key) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; rank&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(r-&gt;l, key);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; sz&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(r-&gt;l) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; rank&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(r-&gt;r, key);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;일반 BST 삽입/삭제 시 &lt;code&gt;update(n)&lt;/code&gt; 로 size 를 갱신한다.&lt;/p&gt;
&lt;h2 id=&quot;구현-c-policy-based-tree&quot;&gt;구현: C++ policy-based tree&lt;/h2&gt;
&lt;p&gt;경쟁 프로그래밍에서는 GNU PBDS 를 활용한다. 직접 구현보다 훨씬 간단하고 안정적.&lt;/p&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
variants={[
{
language: “cpp”,
label: “C++ (GNU PBDS)”,
code: `#include &amp;#x3C;bits/stdc++.h&gt;
#include &amp;#x3C;ext/pb_ds/assoc_container.hpp&gt;
#include &amp;#x3C;ext/pb_ds/tree_policy.hpp&gt;
using namespace std;
using namespace __gnu_pbds;&lt;/p&gt;
&lt;p&gt;typedef tree&amp;#x3C;int, null_type, less&lt;int&gt;,
rb_tree_tag, tree_order_statistics_node_update&gt; OST;&lt;/int&gt;&lt;/p&gt;
&lt;p&gt;int main() {
OST ost;
// 삽입
ost.insert(3); ost.insert(1); ost.insert(7);
ost.insert(5); ost.insert(9);&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// find_by_order(k): k번째 원소 (0-indexed)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cout &amp;#x3C;&amp;#x3C; *ost.find_by_order(0) &amp;#x3C;&amp;#x3C; &quot;\\n&quot;;  // 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cout &amp;#x3C;&amp;#x3C; *ost.find_by_order(2) &amp;#x3C;&amp;#x3C; &quot;\\n&quot;;  // 5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// order_of_key(x): x보다 작은 원소 수 (= rank)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cout &amp;#x3C;&amp;#x3C; ost.order_of_key(5) &amp;#x3C;&amp;#x3C; &quot;\\n&quot;;  // 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cout &amp;#x3C;&amp;#x3C; ost.order_of_key(8) &amp;#x3C;&amp;#x3C; &quot;\\n&quot;;  // 4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 삭제&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ost.erase(7);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cout &amp;#x3C;&amp;#x3C; ost.size() &amp;#x3C;&amp;#x3C; &quot;\\n&quot;;  // 4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;return 0;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;code&gt;,     },     {       language: &quot;python&quot;,       label: &quot;Python (SortedList)&quot;,       code: &lt;/code&gt;# Python: sortedcontainers.SortedList 로 유사 기능
from sortedcontainers import SortedList&lt;/p&gt;
&lt;p&gt;sl = SortedList([3, 1, 7, 5, 9])&lt;/p&gt;
&lt;h1 id=&quot;select-k번째-원소-0-indexed&quot;&gt;select: k번째 원소 (0-indexed)&lt;/h1&gt;
&lt;p&gt;print(sl[0])   # 1
print(sl[2])   # 5&lt;/p&gt;
&lt;h1 id=&quot;rank-x보다-작은-원소-수&quot;&gt;rank: x보다 작은 원소 수&lt;/h1&gt;
&lt;p&gt;print(sl.bisect_left(5))  # 2
print(sl.bisect_left(8))  # 4&lt;/p&gt;
&lt;h1 id=&quot;삽입삭제&quot;&gt;삽입/삭제&lt;/h1&gt;
&lt;p&gt;sl.remove(7)
print(len(sl))  # 4&lt;code&gt;,     },   ]}   cases={[     {       label: &quot;rank/select 예시&quot;,       input: ``,       output: &lt;/code&gt;1
5
2
4
4`,
},
]}
/&gt;&lt;/p&gt;
&lt;h2 id=&quot;복잡도&quot;&gt;복잡도&lt;/h2&gt;









































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;연산&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;OST (균형)&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;정렬 배열&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;일반 BST&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;삽입&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(log N)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(log N) 평균&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;삭제&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(log N)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(log N) 평균&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;rank(x)&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(log N)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(log N)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(log N) 평균&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;select(k)&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(log N)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(1)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(log N) 평균&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;공간&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;언어별-지원-현황&quot;&gt;언어별 지원 현황&lt;/h2&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;언어&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;라이브러리&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;지원 여부&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;C++&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;__gnu_pbds::tree&lt;/code&gt; (GCC)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;✅&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Python&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;sortedcontainers.SortedList&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;유사 (bisect 활용)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Java&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;TreeMap.headMap().size()&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;유사 (O(log N) 아님)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Rust&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;std::collections::BTreeSet&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;미지원, 직접 구현&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
C++ &lt;code&gt;__gnu_pbds::tree&lt;/code&gt; 는 &lt;strong&gt;중복 원소를 허용하지 않는다&lt;/strong&gt;. 중복이 있으면 &lt;code&gt;pair&amp;#x3C;int,int&gt;&lt;/code&gt; 로 wrapping (value, unique_id).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;함정&quot;&gt;함정&lt;/h2&gt;
&lt;h3 id=&quot;1-중복-원소&quot;&gt;1. 중복 원소&lt;/h3&gt;
&lt;p&gt;GNU PBDS &lt;code&gt;tree&amp;#x3C;int, ...&gt;&lt;/code&gt; 는 set 기반이라 중복 삽입 무시.&lt;/p&gt;
&lt;p&gt;중복 처리: &lt;code&gt;pair&amp;#x3C;int, int&gt;&lt;/code&gt; 로 (값, 고유번호) 저장.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;typedef&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; tree&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;pair&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, null_type, less&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;pair&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;             rb_tree_tag, tree_order_statistics_node_update&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; OST;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; uid &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ost.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;insert&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, uid&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ost.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;insert&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, uid&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // 중복 허용&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2-size-갱신-누락&quot;&gt;2. size 갱신 누락&lt;/h3&gt;
&lt;p&gt;직접 구현 시 삽입/삭제 재귀 리턴 경로에서 &lt;code&gt;update(n)&lt;/code&gt; 빠뜨리면 rank/select 오류. 회전 시도 포함해서 갱신 필요.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
Treap 회전 시 회전 후 자식부터 &lt;code&gt;update()&lt;/code&gt; 해야 부모 size 가 정확해진다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;3-fenwick-tree-대안&quot;&gt;3. Fenwick Tree 대안&lt;/h3&gt;
&lt;p&gt;좌표 압축 후 Fenwick Tree 로 rank/select 를 O(log N) 에 처리 가능. 삽입/삭제 범위가 정해진 경우 더 빠름. [[fenwick-tree|Fenwick Tree]] 참조.&lt;/p&gt;
&lt;h2 id=&quot;boj-연습-문제&quot;&gt;BOJ 연습 문제&lt;/h2&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;번호&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;제목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;비고&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1539&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;이진 검색 트리&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;rank/select 연습&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 7469&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;K번째 수&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OST 또는 Merge Sort Tree&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 2042&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;구간 합 구하기&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Fenwick Tree 대안 비교&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[binary-search-tree|BST (이진 탐색 트리)]]&lt;/li&gt;
&lt;li&gt;[[bbst|Treap (Balanced BST)]]&lt;/li&gt;
&lt;li&gt;[[fenwick-tree|Fenwick Tree]] (좌표 압축 후 rank/select 대체)&lt;/li&gt;
&lt;li&gt;[[segtree|Segment Tree]] (구간 쿼리)&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>algorithm</category><category>data-structure</category><category>tree</category><category>bst</category><category>balanced-tree</category><author>koa (김신건)</author></item><item><title>Pascal Triangle: 이항계수</title><link>https://shinkeonkim.com/wiki/algorithm/math/pascal-triangle/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/algorithm/math/pascal-triangle/</guid><pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Pascal Triangle&lt;/strong&gt; 은 각 원소가 위 두 원소의 합인 삼각형. 행 n, 열 k 의 원소가 이항계수 $\binom{n}{k}$.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1 2 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1 3 3 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1 4 6 4 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1 5 10 10 5 1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이항계수 $\binom{n}{k}$: n 개 중 k 개를 순서 없이 선택하는 경우의 수.&lt;/p&gt;
&lt;p&gt;$$
\binom{n}{k} = \frac{n!}{k! \cdot (n-k)!}
$$&lt;/p&gt;
&lt;h2 id=&quot;문제-상황&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p&gt;$\binom{n}{k}$ 를 팩토리얼로 직접 계산하면:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$n!$ 이 빠르게 오버플로우&lt;/li&gt;
&lt;li&gt;MOD 연산 시 나눗셈 처리 복잡&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;해결&lt;/strong&gt;: 점화식 DP 로 MOD 아래서 정확하게 계산.&lt;/p&gt;
&lt;p&gt;재귀 점화식:&lt;/p&gt;
&lt;p&gt;$$
\binom{n}{k} = \binom{n-1}{k-1} + \binom{n-1}{k}
$$&lt;/p&gt;
&lt;p&gt;경계 조건: $\binom{n}{0} = \binom{n}{n} = 1$.&lt;/p&gt;
&lt;h2 id=&quot;시각화&quot;&gt;시각화&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C00[&quot;C(0,0) = 1&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C10[&quot;C(1,0) = 1&quot;] --&gt; C20[&quot;C(2,0) = 1&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C00 --&gt; C10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C11[&quot;C(1,1) = 1&quot;] --&gt; C21[&quot;C(2,1) = 2&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C10 --&gt; C21&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C00 --&gt; C11&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C11 --&gt; C22[&quot;C(2,2) = 1&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C21 --&gt; C31[&quot;C(3,1) = 3&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C20 --&gt; C31&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C21 --&gt; C32[&quot;C(3,2) = 3&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C22 --&gt; C32&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C20 --&gt; C30[&quot;C(3,0) = 1&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C22 --&gt; C33[&quot;C(3,3) = 1&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;핵심-아이디어&quot;&gt;핵심 아이디어&lt;/h2&gt;
&lt;h3 id=&quot;dp-계산&quot;&gt;DP 계산&lt;/h3&gt;
&lt;p&gt;2D 배열 &lt;code&gt;C[i][j]&lt;/code&gt;에 파스칼 삼각형 저장. 이전 행에서 두 값을 더하는 O(N^2) 방법.&lt;/p&gt;
&lt;p&gt;단일 쿼리라면 1D 배열로 공간 최적화:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;배열을 오른쪽에서 왼쪽으로 갱신 → in-place 업데이트&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;mod-처리&quot;&gt;MOD 처리&lt;/h3&gt;
&lt;p&gt;대부분 문제는 답을 $10^9 + 7$ 로 나눈 나머지 요구.&lt;/p&gt;
&lt;p&gt;DP 점화식은 덧셈만 사용하므로 MOD 연산 자연스럽게 적용:&lt;/p&gt;
&lt;p&gt;$$
C[i][j] = (C[i-1][j-1] + C[i-1][j]) \bmod M
$$&lt;/p&gt;
&lt;h3 id=&quot;lucas-theorem-대형-n&quot;&gt;Lucas’ Theorem (대형 n)&lt;/h3&gt;
&lt;p&gt;n 이 매우 크고 소수 p 로 나눈 나머지를 구할 때:&lt;/p&gt;
&lt;p&gt;$$
\binom{n}{k} \equiv \prod_{i} \binom{n_i}{k_i} \pmod{p}
$$&lt;/p&gt;
&lt;p&gt;여기서 $n = \sum n_i p^i$, $k = \sum k_i p^i$ 는 p 진법 표현.&lt;/p&gt;
&lt;h2 id=&quot;알고리즘&quot;&gt;알고리즘&lt;/h2&gt;
&lt;h3 id=&quot;알고리즘-1-2d-파스칼-삼각형-on2-공간시간&quot;&gt;알고리즘 1: 2D 파스칼 삼각형 (O(N^2) 공간/시간)&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// C[i][j] = C(i, j) mod MOD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; MOD &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;9&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 7&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;vector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;vector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;long&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; long&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&gt; &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;C&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;n&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;vector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;long&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; long&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;(n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n; i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C[i][&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; j &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; j &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i; j&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        C[i][j] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (C[i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;][j&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; C[i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;][j]) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; MOD;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// C[n][k] = C(n, k) mod MOD&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;알고리즘-2-1d-공간-최적화-on-공간&quot;&gt;알고리즘 2: 1D 공간 최적화 (O(N) 공간)&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;vector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;long&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; long&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt; &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;C&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;n&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;C[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n; i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; j &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i; j &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; j&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;--&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // 오른쪽에서 왼쪽으로&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        C[j] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (C[j] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; C[j&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; MOD;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// C[k] = C(n, k) mod MOD&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;알고리즘-3-팩토리얼--역원-단일-쿼리-on-전처리&quot;&gt;알고리즘 3: 팩토리얼 + 역원 (단일 쿼리 O(N) 전처리)&lt;/h3&gt;
&lt;p&gt;n 이 크고 다중 쿼리일 때: 팩토리얼과 모듈러 역원을 전처리.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; MAXN &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 5&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; MOD &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;9&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 7&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;long&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; long&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; fact[MAXN], inv_fact[MAXN];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;long&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; long&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; power&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;long&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; long&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; a&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;long&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; long&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; b&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;long&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; long&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; mod&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    long&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; long&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; res &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; a &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;%=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; mod;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (; b &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; b &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (b &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) res &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; res &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; a &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; mod;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        a &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; a &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; a &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; mod;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; res;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; precompute&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; n&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    fact[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n; i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) fact[i] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; fact[i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; MOD;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    inv_fact[n] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; power&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(fact[n], MOD &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, MOD);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;--&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) inv_fact[i] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; inv_fact[i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; MOD;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;long&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; long&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; C&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; n&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; k&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (k &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ||&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; k &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; fact[n] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; MOD &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; inv_fact[k] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; MOD &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; inv_fact[n&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;k] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; MOD;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;구현&quot;&gt;구현&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
variants={[
{
language: “cpp”,
label: “C++”,
code: `#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;
const int MOD = 1e9 + 7;&lt;/p&gt;
&lt;p&gt;int main() {
int n;
cin &gt;&gt; n;
// 파스칼 삼각형 출력
vector&lt;long long=&quot;&quot;&gt; C(n + 1, 0);
C[0] = 1;
for (int i = 0; i &amp;#x3C; n; i++) {
vector&lt;long long=&quot;&quot;&gt; nxt(n + 1, 0);
for (int j = 0; j &amp;#x3C;= i; j++) {
nxt[j] = (nxt[j] + C[j]) % MOD;
nxt[j + 1] = (nxt[j + 1] + C[j]) % MOD;
}
C = nxt;
for (int j = 0; j &amp;#x3C;= i + 1; j++)
cout &amp;#x3C;&amp;#x3C; C[j] &amp;#x3C;&amp;#x3C; ” \n”[j == i + 1];
}
// C(n, k) 출력
int k;
cin &gt;&gt; k;
cout &amp;#x3C;&amp;#x3C; C[k] &amp;#x3C;&amp;#x3C; “\n”;
return 0;
}&lt;code&gt;,     },     {       language: &quot;python&quot;,       label: &quot;Python&quot;,       code: &lt;/code&gt;MOD = 10**9 + 7&lt;/long&gt;&lt;/long&gt;&lt;/p&gt;
&lt;p&gt;def pascal(n):
C = [0] * (n + 1)
C[0] = 1
rows = []
rows.append(C[:1])
for i in range(1, n + 1):
nxt = [0] * (n + 1)
for j in range(i + 1):
if j &gt; 0: nxt[j] = (nxt[j] + C[j-1]) % MOD
nxt[j] = (nxt[j] + C[j]) % MOD
C = nxt
rows.append(C[:i+1])
return C, rows&lt;/p&gt;
&lt;p&gt;n = int(input())
C, rows = pascal(n)
for row in rows:
print(*row)
k = int(input())
print(C[k])&lt;code&gt;,     },   ]}   cases={[     {       label: &quot;기본 (n=5, k=2)&quot;,       input: &lt;/code&gt;5
2&lt;code&gt;,       output: &lt;/code&gt;1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
10`,
},
]}
/&gt;&lt;/p&gt;
&lt;h2 id=&quot;성질&quot;&gt;성질&lt;/h2&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;성질&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;수식&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;행 합&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;$\sum_{k=0}^{n} \binom{n}{k} = 2^n$&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;n 개 원소 부분집합 수&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;홀짝 행 합&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;$\sum_{k \text{ even}} \binom{n}{k} = 2^{n-1}$&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;짝수 선택 수&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Hockey stick&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;$\sum_{i=r}^{n} \binom{i}{r} = \binom{n+1}{r+1}$&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;대각선 합&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Vandermonde&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;$\sum_{k} \binom{m}{k}\binom{n}{r-k} = \binom{m+n}{r}$&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;합성&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;대칭&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;$\binom{n}{k} = \binom{n}{n-k}$&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;삼각형 대칭&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;복잡도&quot;&gt;복잡도&lt;/h2&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;방법&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;시간&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;공간&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;용도&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;2D DP&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;$O(N^2)$&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;$O(N^2)$&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;소규모, n 전체 테이블&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;1D DP&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;$O(N^2)$&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;$O(N)$&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;공간 절약&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;팩토리얼 전처리&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;$O(N)$ 전처리, $O(1)$ 쿼리&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;$O(N)$&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;다중 쿼리, 대규모&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Lucas’ Theorem&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;$O(\log_p N)$ 쿼리&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;$O(p)$&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;n 극히 크고 소수 MOD&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;함정&quot;&gt;함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
&lt;strong&gt;MOD 로 나눈 후 나눗셈 불가&lt;/strong&gt;: $\binom{n}{k} \neq \frac{fact[n]}{fact[k] \cdot fact[n-k]}$ MOD 세계에서는 직접 나눗셈 대신 모듈러 역원 사용.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
&lt;strong&gt;k &gt; n 이면 0&lt;/strong&gt;: 경계 조건 $\binom{n}{k} = 0$ $(k &gt; n$ 또는 $k &amp;#x3C; 0)$ 를 반드시 처리.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
&lt;strong&gt;오버플로우&lt;/strong&gt;: &lt;code&gt;fact[n]&lt;/code&gt; 을 long long 으로 선언해도 n 이 크면 &lt;code&gt;fact[n] * inv_fact[k]&lt;/code&gt; 중간에 오버플로우. 각 곱셈마다 &lt;code&gt;% MOD&lt;/code&gt; 적용.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;흔한-실수&quot;&gt;흔한 실수&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;C[i][j]&lt;/code&gt; 인덱스 오류: j &gt; i 일 때 0 처리 안 함&lt;/li&gt;
&lt;li&gt;1D DP 갱신 시 왼쪽에서 오른쪽으로 갱신하면 이미 갱신된 값 사용 (오류)&lt;/li&gt;
&lt;li&gt;대형 n 에서 DP 테이블 메모리 초과, 팩토리얼 방법 사용 안 함&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;boj-연습-문제&quot;&gt;BOJ 연습 문제&lt;/h2&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;번호&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;제목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;키워드&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 11050&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;이항 계수 1&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;기본 계산&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 11051&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;이항 계수 2&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;MOD 계산&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 2608&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;파스칼의 삼각형&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;삼각형 출력&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 1862&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;2진수 표현&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;이항계수 응용&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 13977&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;이항 계수와 쿼리&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;팩토리얼 전처리&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;BOJ 11401&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;이항 계수 3&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;큰 n, 역원&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[combinatorics|조합론]]&lt;/li&gt;
&lt;li&gt;[[catalan-number|Catalan Number]]&lt;/li&gt;
&lt;li&gt;[[inclusion-exclusion|Inclusion-Exclusion]]&lt;/li&gt;
&lt;li&gt;[[arithmetic|산술 기초]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>algorithm</category><category>math</category><category>combinatorics</category><category>dp</category><author>koa (김신건)</author></item><item><title>Persistent Segment Tree (영속 세그): 시점별 스냅샷</title><link>https://shinkeonkim.com/wiki/algorithm/data-structure/persistent-segtree/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/algorithm/data-structure/persistent-segtree/</guid><pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Persistent Segment Tree&lt;/strong&gt; 는 매 갱신마다 &lt;strong&gt;이전 버전을 그대로 유지&lt;/strong&gt;하면서 새 버전을 만드는 세그먼트 트리입니다. 각 버전은 &lt;strong&gt;루트 포인터&lt;/strong&gt; 로 식별됩니다.&lt;/p&gt;
&lt;p&gt;핵심 트릭: &lt;strong&gt;path copying&lt;/strong&gt;. 갱신 경로 (O(log N) 노드) 만 복사하고 나머지는 이전 버전과 공유.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;공간&lt;/strong&gt;: 갱신 1 회당 O(log N) 노드 추가&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;시간&lt;/strong&gt;: 조회/갱신 모두 O(log N)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;문제-상황&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p&gt;배열에서 아래 두 가지를 동시에 처리해야 할 때:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;과거 버전 조회&lt;/strong&gt;: “버전 v 시점의 구간 합을 구하라”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;K-th smallest in range&lt;/strong&gt;: 구간 [l, r] 에서 k 번째 작은 값을 O(log N) 에&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;단순 세그먼트 트리는 현재 상태만 저장 → 과거 조회 불가.&lt;/p&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;방식&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;갱신 1회 추가 공간&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;시간&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;과거 조회&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;배열 전체 복사&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(log N)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;✅&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Persistent Segment Tree&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(log N)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(log N)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;✅&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;기본 세그먼트 트리&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(1)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(log N)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;❌&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;시각화&quot;&gt;시각화&lt;/h2&gt;
&lt;p&gt;버전 0 에서 위치 i 를 갱신해 버전 1 을 만드는 path copying 과정.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph v0[&quot;버전 0&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        A0[&quot;루트 v0&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        B0[&quot;내부 v0-L&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        C0[&quot;내부 v0-R&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        D0[&quot;리프 i v0&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        E0[&quot;리프 i+1 v0&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph v1[&quot;버전 1, 위치 i 갱신&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        A1[&quot;루트 v1 (새 노드)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        B1[&quot;내부 v1-L (새 노드)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        D1[&quot;리프 i v1 (새 노드)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A0 --&gt; B0 &amp;#x26; C0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B0 --&gt; D0 &amp;#x26; E0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A1 --&gt;|&quot;복사된 경로&quot;| B1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A1 --&gt;|&quot;공유&quot;| C0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B1 --&gt;|&quot;복사된 경로&quot;| D1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B1 --&gt;|&quot;공유&quot;| E0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    style A1 fill:#d4edda&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    style B1 fill:#d4edda&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    style D1 fill:#d4edda&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;초록 노드만 새로 할당. &lt;code&gt;C0&lt;/code&gt;, &lt;code&gt;E0&lt;/code&gt; 는 두 버전이 공유해 공간을 절약.&lt;/p&gt;
&lt;h2 id=&quot;핵심-아이디어&quot;&gt;핵심 아이디어&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;path copying&lt;/strong&gt;: 갱신이 필요한 루트에서 리프까지의 경로만 새 노드로 복사. 나머지 자식은 이전 버전 노드를 그대로 가리킴.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;버전 v 루트: root[v]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;버전 v+1 생성:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  1. 루트 노드 새로 할당 (기존 복사)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  2. 갱신 방향 자식: 재귀적으로 새 노드 생성&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  3. 반대 방향 자식: 이전 버전 노드 포인터 그대로 유지&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  4. root[v+1] = 새 루트&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;두 버전의 차이로 범위 쿼리&lt;/strong&gt;: &lt;code&gt;query(root[l-1], root[r], ...)&lt;/code&gt; 처럼 두 버전 PST 의 합 차이를 이용하면 구간 통계를 O(log N) 에 계산.&lt;/p&gt;
&lt;h2 id=&quot;알고리즘&quot;&gt;알고리즘&lt;/h2&gt;
&lt;h3 id=&quot;build&quot;&gt;Build&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;build(l, r):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    id = 새 노드 할당&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if l == r: return id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    m = (l + r) / 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    node[id].left  = build(l, m)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    node[id].right = build(m+1, r)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return id&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;update-path-copying&quot;&gt;Update (path copying)&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;update(prev, l, r, pos, val):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    id = 새 노드 (pool[prev] 복사)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    node[id].sum += val&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if l == r: return id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    m = (l + r) / 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if pos &amp;#x3C;= m:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        node[id].left  = update(node[prev].left,  l, m, pos, val)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    else:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        node[id].right = update(node[prev].right, m+1, r, pos, val)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    node[id].sum = node[node[id].left].sum + node[node[id].right].sum&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return id&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;query-두-버전-차이&quot;&gt;Query (두 버전 차이)&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;query(u, v, l, r, L, R):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // node[v] - node[u] = 이 두 버전 사이 갱신 합&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if L &amp;#x3C;= l and r &amp;#x3C;= R:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        return node[v].sum - node[u].sum&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    m = (l + r) / 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    res = 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if L &amp;#x3C;= m: res += query(node[u].left,  node[v].left,  l, m, L, R)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if R &gt; m:  res += query(node[u].right, node[v].right, m+1, r, L, R)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return res&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;k-th-smallest-in-range&quot;&gt;K-th Smallest in Range&lt;/h3&gt;
&lt;p&gt;좌표 압축 후, &lt;code&gt;a[1..i]&lt;/code&gt; 에 값 삽입한 누적 PST 를 구축.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;kth(u, v, l, r, k):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // [l-1 버전]과 [r 버전] 차이로 구간 원소 카운트&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if l == r: return l   // 좌표 압축값 -&gt; 실제 값 복원&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    m = (l + r) / 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    left_cnt = node[node[v].left].sum - node[node[u].left].sum&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if k &amp;#x3C;= left_cnt:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        return kth(node[u].left,  node[v].left,  l, m, k)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    else:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        return kth(node[u].right, node[v].right, m+1, r, k - left_cnt)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;구현&quot;&gt;구현&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
language=“cpp”
label=“C++ (PST, K-th Smallest)”
outputLanguage=“text”
outputLabel=“결과”
title=“BOJ 7469: K번째 수 (구간 k-th smallest)”
code={`#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;&lt;/p&gt;
&lt;p&gt;const int MAXN = 6000005;
struct Node { int l, r, sum; } pool[MAXN];
int root[100005], sz;&lt;/p&gt;
&lt;p&gt;int build(int l, int r) {
int id = ++sz;
pool[id] = {0, 0, 0};
if (l == r) return id;
int m = (l + r) &gt;&gt; 1;
pool[id].l = build(l, m);
pool[id].r = build(m + 1, r);
return id;
}&lt;/p&gt;
&lt;p&gt;int update(int prev, int l, int r, int pos) {
int id = ++sz;
pool[id] = pool[prev];
pool[id].sum++;
if (l == r) return id;
int m = (l + r) &gt;&gt; 1;
if (pos &amp;#x3C;= m) pool[id].l = update(pool[prev].l, l, m, pos);
else          pool[id].r = update(pool[prev].r, m+1, r, pos);
return id;
}&lt;/p&gt;
&lt;p&gt;int kth(int u, int v, int l, int r, int k) {
if (l == r) return l;
int m = (l + r) &gt;&gt; 1;
int left_cnt = pool[pool[v].l].sum - pool[pool[u].l].sum;
if (k &amp;#x3C;= left_cnt) return kth(pool[u].l, pool[v].l, l, m, k);
return kth(pool[u].r, pool[v].r, m+1, r, k - left_cnt);
}&lt;/p&gt;
&lt;p&gt;int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, q;
cin &gt;&gt; n &gt;&gt; q;
vector&lt;int&gt; a(n+1);
vector&lt;int&gt; coords;
for (int i = 1; i &amp;#x3C;= n; i++) {
cin &gt;&gt; a[i];
coords.push_back(a[i]);
}
sort(coords.begin(), coords.end());
coords.erase(unique(coords.begin(), coords.end()), coords.end());
int M = (int)coords.size();
auto comp = [&amp;#x26;](int x) {
return (int)(lower_bound(coords.begin(), coords.end(), x)
- coords.begin()) + 1;
};
root[0] = build(1, M);
for (int i = 1; i &amp;#x3C;= n; i++)
root[i] = update(root[i-1], 1, M, comp(a[i]));
while (q—) {
int l, r, k;
cin &gt;&gt; l &gt;&gt; r &gt;&gt; k;
int idx = kth(root[l-1], root[r], 1, M, k);
cout &amp;#x3C;&amp;#x3C; coords[idx-1] &amp;#x3C;&amp;#x3C; “\n”;
}
return 0;
}&lt;code&gt;}   output={&lt;/code&gt;// 예: n=5, a=[1,5,2,6,3], 쿼리: l=2 r=5 k=2
// 구간 [2,5] = {5,2,6,3} 정렬 -&gt; {2,3,5,6}
// 2번째 = 3
3`}
/&gt;&lt;/int&gt;&lt;/int&gt;&lt;/p&gt;
&lt;h2 id=&quot;복잡도&quot;&gt;복잡도&lt;/h2&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;연산&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;시간&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;추가 공간&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Build&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(N)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Update 1회&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(log N)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(log N) 노드&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Query&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(log N)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(1)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;K-th 1회&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(log N)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O(1)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;전체 공간&lt;/strong&gt;: O((N + Q) log N). 좌표 압축 값 범위 M 에 대해 초기 build O(M).&lt;/p&gt;
&lt;p&gt;pool 배열 크기: 최소 &lt;code&gt;M + Q * log(M)&lt;/code&gt;. 실전에서는 &lt;code&gt;N * 40&lt;/code&gt; 이 안전.&lt;/p&gt;
&lt;h2 id=&quot;함정&quot;&gt;함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
&lt;strong&gt;pool 크기 과소 추정&lt;/strong&gt;: 갱신 1 회당 log N 노드 소모. N = 10^5, Q = 10^5, log N ≈ 17 이면 최소 340 만. 여유 있게 &lt;code&gt;600 만&lt;/code&gt; 이상 잡는다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
&lt;strong&gt;kth 에서 버전 순서 혼동&lt;/strong&gt;: &lt;code&gt;kth(root[l-1], root[r], ...)&lt;/code&gt; 에서 &lt;code&gt;root[l-1]&lt;/code&gt; 이 “빼는” 기준 버전. 순서가 바뀌면 left_cnt 가 음수가 되어 WA.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
&lt;strong&gt;좌표 압축 없이 값 범위가 클 때&lt;/strong&gt;: 값 범위 1~10^9 이면 build 단계가 O(10^9) → MLE/TLE. 반드시 좌표 압축하거나 [[dynamic-segtree|Dynamic Segtree]] 를 사용.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;merge sort tree 는 구현이 단순하지만 O(log²N). PST 는 O(log N) 이지만 pool 관리 필요.&lt;/li&gt;
&lt;li&gt;다차원 PST 는 공간 복잡도가 기하급수로 늘어남 (실전에서 대부분 [[wavelet-tree|Wavelet Tree]] 대체).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;boj&quot;&gt;BOJ&lt;/h2&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;문제&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;설명&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/7469&quot;&gt;BOJ 7469 K번째 수&lt;/a&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;구간 k-th smallest, 대표 문제&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/16978&quot;&gt;BOJ 16978 수열과 쿼리 22&lt;/a&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;과거 버전 구간 합 조회&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/13557&quot;&gt;BOJ 13557 수열과 쿼리 10&lt;/a&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;구간 최대 부분합 + 버저닝&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/8904&quot;&gt;BOJ 8904 2차원 랜덤 게임&lt;/a&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;머지 소트 트리 vs PST 비교&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[segtree|Segment Tree]] (기반 자료구조)&lt;/li&gt;
&lt;li&gt;[[wavelet-tree|Wavelet Tree]] (k-th in range 대안)&lt;/li&gt;
&lt;li&gt;[[dynamic-segtree|Dynamic Segtree]] (좌표 압축 없이 사용)&lt;/li&gt;
&lt;li&gt;[[merge-sort-tree|Merge Sort Tree]] (k-th 대안, O(log²N))&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>algorithm</category><category>data-structure</category><category>segment-tree</category><category>persistent</category><author>koa (김신건)</author></item></channel></rss>