<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>cgiosy.dev</title>
    <link>https://cgiosy.tistory.com/</link>
    <description>웹 개발, CTF, PS 및 알고리즘 관련 글을 정리해 포스팅합니다.</description>
    <language>ko</language>
    <pubDate>Thu, 21 May 2026 23:29:40 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>cgiosy</managingEditor>
    <image>
      <title>cgiosy.dev</title>
      <url>https://tistory1.daumcdn.net/tistory/2994983/attach/ae9d14d982864cbd92b939597f514693</url>
      <link>https://cgiosy.tistory.com</link>
    </image>
    <item>
      <title>Reply Code Challenge 2023 - Teen Edition</title>
      <link>https://cgiosy.tistory.com/entry/Reply-Code-Challenge-2023-Teen-Edition</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-03-11 오후 10.17.53.png&quot; data-origin-width=&quot;2198&quot; data-origin-height=&quot;1170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E652o/btr3bd4UbmM/KL6KeXRrWifuzqDluvKPOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E652o/btr3bd4UbmM/KL6KeXRrWifuzqDluvKPOk/img.png&quot; data-alt=&quot;빈집털이에 성공한 모습이다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E652o/btr3bd4UbmM/KL6KeXRrWifuzqDluvKPOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE652o%2Fbtr3bd4UbmM%2FKL6KeXRrWifuzqDluvKPOk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2198&quot; height=&quot;1170&quot; data-filename=&quot;스크린샷 2023-03-11 오후 10.17.53.png&quot; data-origin-width=&quot;2198&quot; data-origin-height=&quot;1170&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;빈집털이에 성공한 모습이다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://challenges.reply.com/tamtamy/challenges/category/coding_teen#about&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://challenges.reply.com/tamtamy/challenges/category/coding_teen#about&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3월 3일에 &lt;a href=&quot;https://www.acmicpc.net/user/junseo&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;junseo&lt;/a&gt;가 웬 처음 보는 대회를 들고 여기 같이 해보자며 팀하자고 DM이 왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;귀찮아서 좀 고민하다가 상금을 봤는데, 1등 5000 유로 / 2등 2000 유로 / 3등 1000 유로씩 준다길래 덥썩 물었다. 온라인 대회에 이 수준이면 상당히 후한 축에 속한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대충 보니까 아웃풋 온리에 휴리스틱 대회 같아서 조금 불안했는데, 내가 아는 휴리스틱이라곤 '어떤 알고리즘'밖에 없기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무슨 이상한 가지치기나 애드혹 관찰을 섞은 컨스트럭티브, 플로우 어쩌구로 최적화해야 하는 게 아닌가 싶어 좀 쫄았는데... 다행히 이는 기우였음이 드러났다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 대회들을 풀어보며 연습해야 하는 게 아닐까 싶었지만 결국 안 했다. 이대로 괜찮은 거 맞나? 란 생각이 들었는데 괜찮더라. :)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대회 이름답게 만 19세 이하인 사람만 참가 가능하고, 팀은 최대 4인으로 구성할 수 있다. 나와 준서 말고도 &lt;a href=&quot;https://www.acmicpc.net/user/hibye1217&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;hibye1217&lt;/a&gt;이 팀원으로 있어서 한 명을 더 구할 수 있었다. (참고로 &lt;a href=&quot;https://blog.naver.com/hibye1217/223040354091&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;hibye씨의 블로그&lt;/a&gt;에도 후기가 있다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 난 내 나이 이하인 아는 사람 중에 휴리스틱 잘 할 만한 사람이 없었고 (jjang씨가 잘한다고 듣긴 했는데 친분이 없었다), 준서도 별 소식이 없어서 3인 팀으로 나갔다. 지금 생각해보면 한 명 더 구했어도 1등이나 2등을 했을 거란 보장은 없던 것 같아서 괜찮은 선택인 듯?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팀명은 준서가 지었다. 딱히 의미는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대회 시간은 한국 기준 [3월 10일 오전 00:30 ~ 오전 04:30]이었고, 5문제 대회였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시작할 때 하이바이가 A, 준서가 B, 내가 C를 잡기로 했다. 지금 보면 문제 선택도 꽤 운이 좋았던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 아는 유일한 휴리스틱 알고리즘, &lt;a href=&quot;https://gist.github.com/cgiosy/ed16f4988eeb7e989a97644fe61e1561&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;DLAS&lt;/a&gt;를 미리 준비해놓고, 혹시 랜덤 수 생성 속도가 발목을 잡을까 봐 &lt;a href=&quot;https://gist.github.com/cgiosy/12cd39c3d58e4c4250caf9173d08165b&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;커스텀 RNG 구현&lt;/a&gt; (Wyhash에 이것저것 섞었다)도 준비해놨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대회가 시작되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(시간은 대회 시작 이후 흐른 시간 기준이다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;00:00&lt;/b&gt; - 사전에 정한 대로, 각자 문제를 잡았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 C를 봤고, 문제를 요약하면 다음과 같았다:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;C. W*H 격자에서 꽃 F개를 놓을 수 있는 칸 G개가 주어질 때, 꽃들 간의 최소 맨해튼 거리를 최대화하라.&lt;br /&gt;W, H &amp;le; 150, F &amp;le; G &amp;le; 50&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;음... 뇌정지가 왔다. 거리를 고정하고 파라메트릭으로 잘 찾기? 아니면 맨해튼 거리에서 x+y x-y -x+y -x-y 부호 잘 쪼개서 뭔가 그리디하게 선택해보기? 아니면 DP 식을 잘 세워서 슥슥 처리해보기? 어느 쪽이든 쉽지 않아 보였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;00:12&lt;/b&gt; - B가 풀렸다. (junseo)&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;00:13&lt;/b&gt; - A가 풀렸다. (hibye1217)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 팀원 둘이 연속으로 문제를 풀었다. 난 구현 시작은 커녕 풀이도 명확히 갈피를 못 잡고 있는데, 환장할 노릇이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 고민하다가, 데이터가 약하다는 말들을 믿고 그냥 DLAS를 믿고 해보기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;...사실 뭘 한다기도 애매한 게,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3줄짜리 O(N^2) 최대 맨해튼 거리 계산 (O(N)에 되는데, 왠지 필요없을 것 같아서 대충 짰다)&lt;br /&gt;+ 1줄짜리 무작위 꽃 위치 변경 (pos[rng(N)] = rng(M))&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 5분만에 짠 다음 심호흡을 하며, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;주어지는 데이터들을&lt;/span&gt;&amp;nbsp;&lt;b&gt;00:20&lt;/b&gt;부터 하나하나 넣어봤다. 어...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;00:27&lt;/b&gt; - C가 풀렸다. (cgiosy)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당황스러울 만큼 빠르게 답이 수렴했고, 뭐 막히는 부분 없이 그냥 맞았다(...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 데이터는 답이 옳은지 판별을 안 해줘서 이대로 제출해도 되나 좀 고민했는데, DLAS 돌릴 때 제한을 10배쯤 늘리고 줘도 똑같이 나오길래 안심하고 냈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;남은 건 D와 E였는데, 둘 다 쉽지 않아 보였다. 각각 내용은 다음과 같았다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;D. N &amp;times; N 격자에 Q개의 보석이 흩어져 있고, 각 보석은 C가지 색 중 한 가지 색을 가진다.&lt;br /&gt;모든 색을 하나 이상 포함하는 직사각형 중, 가로 &amp;times; 세로 넓이가 최소인 것을 구하여라.&lt;br /&gt;N &amp;le; 1500, Q &amp;le; 17500, C &amp;le; 52&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;E. N &amp;times; N 격자의 각 칸에 임의의 높이를 가진 빌딩이 세워져 있다. 다음 정보들이 주어질 때, 각 칸에 세워진 빌딩들의 높이를 구하여라.&lt;br /&gt;N &amp;le; 14&lt;br /&gt;- N &amp;times; N개의 각 칸에서, 인접한 8방향 칸에 있고 높이가 자신보다 큰 빌딩의 개수가 주어진다.&lt;br /&gt;- N &amp;times; N개의 각 칸에서, 동서남북 각 방향을 바라볼 때 높이가 자신보다 큰 '보이는 빌딩'의 개수가 주어진다. '보이는 빌딩'은 앞에 자신보다 큰 빌딩이 없는 빌딩만을 말한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;D는 &lt;a href=&quot;https://www.acmicpc.net/problem/20193&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;화려한 정사각형&lt;/a&gt;처럼 쓱쓱 하면 될 것 같았는데, 직사각형이라 좀 수정이 필요했다. 어떻게 바꿀지 디테일을 생각해봐야 됐고 구현도 귀찮아 보여서 좀 고민됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 빨리 짜서 되는 데까진 제출해놔야 페널티가 낮기 때문에, 하이바이가 투포인터로 우선 O(N^3 C) 및 O(N^3 lg N) 풀이를 시도했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;00:58&lt;/b&gt; - D가 35% 풀렸다. (hibye1217)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;E는 뭔가 잘 하면 될 것 같이 생겼는데, 진짜 풀기 싫게 생겼다. 준서가&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;0이 처음으로 등장하는 위치는 그 행 또는 열에서 높이가 최대인 빌딩의 위치고, 이걸 기반으로 잘 분할해주다 보면 될 것 같다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;면서 복잡한 백트래킹뇌절풀이를 짜려고 하는 것 같았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 단시간에 코딩이 마무리될 것 같지도 않고 D로 가기엔 영 애매해 보여서 E에 한 번 DLAS를 박아보기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;N도 엄청 작고 휴리스틱이 되게 잘 돌아갈 것 같이 생기긴 했는데, 내가 그 이상의 무지성 코딩을 하고 있어서 좀 조마조마했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;짜고 나니 진짜 문제에 주어진 설명 그대로 짜기만 한 수준이라, 뭔가 최적화를 해야 될 것 같은데? 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 우선 제출해서 맞을 건 맞고 얼마나 느린지 확인하는 게 먼저라고 생각해서 데이터를 넣어봤다. 어...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;01:27&lt;/b&gt; - E가 풀렸다. (cgiosy)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주어지는 데이터 다섯 개 중, 쉬운 거 세 개는 C에서의 경험에 따라 당연히 맞을 거라고 생각했었는데, 네 번째부터 갑자기 확 느려졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'아... 그럼 그렇지...'하고 Ctrl-C를 눌러 정지시키려는 순간, 어? 답이 나왔다. 제출했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시? 하고 다섯 번째 데이터를 넣어봤다. 시간이 더 오래 걸린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'아... 그럼 그렇지...'하고 Ctrl-C를 눌러 정지시키려는 순간, 어? 답이 나왔다. 제출했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C번이랑 달리 정답 판별이 명확히 가능했기 때문에, 틀릴 가능성도 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당황을 넘어 황당할 지경이었으나 일단 D를 풀고 그 이후에 생각해보기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;D는... 쉽지 않았다. 휴리스틱 각이 보이긴 했지만:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- C와는 달리 N과 Q 제한이 훨씬 커서 휴리스틱이 훨씬 느리게 돌 것 같았고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- E와도 다르게 답이 맞는지 직접 검증해볼 수 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 마지막 데이터를 제출할 때 믿음 100%로 내야 한단 뜻이었기에, 휴리스틱을 쓰기엔 영 불안한 감이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;투포인터 기반 풀이는 준서와 하이바이가 열심히 고민하고 있었기 때문에, 난 대충 O(Q^2 C) 풀이가 되나 싶어 짜보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;...20분동안 짰지만 예제가 안 나왔다. 생각해보니 아주 기초적인 조건부터 엇나가고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다름 팀원들이 거의 다 짜가는 것 같길래, 던지고 그냥 둘이 코딩하는 거나 지켜봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후, 준서가 O(N^3 + NQ) 풀이를 짜서 맞았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;02:25&lt;/b&gt; - D가 풀렸다. (junseo)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다섯 문제 올솔브긴 했으나 3등까지가 전부 올솔브 상태였기 때문에 4등이 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-03-10 오전 3.11.13.png&quot; data-origin-width=&quot;1564&quot; data-origin-height=&quot;754&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JCsOK/btr3psrOZzI/T12b5rO9cWpIfA9UkUcOV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JCsOK/btr3psrOZzI/T12b5rO9cWpIfA9UkUcOV0/img.png&quot; data-alt=&quot;^4등^&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JCsOK/btr3psrOZzI/T12b5rO9cWpIfA9UkUcOV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJCsOK%2Fbtr3psrOZzI%2FT12b5rO9cWpIfA9UkUcOV0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1564&quot; height=&quot;754&quot; data-filename=&quot;스크린샷 2023-03-10 오전 3.11.13.png&quot; data-origin-width=&quot;1564&quot; data-origin-height=&quot;754&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;^4등^&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;준서는 작년에 이어 또 4등을 하게 생겼다며 울분을 터뜨리고 있었고, 나 역시 '설마 이걸 한 등수 차이로 상금을 못 받나?' 싶어 좀 속상했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;믿을 건 1 ~ 3위 팀에서 히든 데이터가 터지는 것밖에 기대할 수 없는 상황이었는데, 최종 스코어보드가 대회 종료 직후에 공개되기 때문에 1시간 30분을 추가로 기다려야 결과를 알 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(사실 대회 직후에 공개하는 거면 히든 데이터 있는 대회 치고 빠른 편이긴 하다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 긴장돼서 잠을 못 자고 계속 깨어 있었고, 하이바이도 마찬가지였던 것 같고, 준서는 자러 간다...고 해놓고 잠이 안 온다며 돌아와서는 같이 결과를 기다리고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 대회가 끝나 오전 04:30이 되자마자 사이트를 새로고침했다. 어라? 스코어보드에 변동이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;'아... 그럼 그렇지...'하고&lt;span&gt; 단념을 하려는 순간, 생각해보니 히든 데이터가 맞아서 점수가 올라가야 하는데 순위라면 몰라도 변동이 전혀 없는 건 말이 안 됐다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;그러니까, 아직&amp;nbsp;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;스코어보드가 업데이트되지 않은 상태였다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;떨리는 심정으로 3초마다 새로고침을 누르는 것만 2 ~ 3분째, 마침내 스코어보드에 변화가 생겼고, 그 결과는...!&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2023-03-10_4.32.59.png&quot; data-origin-width=&quot;1696&quot; data-origin-height=&quot;1164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6PGc3/btr3cJCaLgB/tZP8kTaO4DKtLKrYZFTKa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6PGc3/btr3cJCaLgB/tZP8kTaO4DKtLKrYZFTKa0/img.png&quot; data-alt=&quot;와!!!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6PGc3/btr3cJCaLgB/tZP8kTaO4DKtLKrYZFTKa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6PGc3%2Fbtr3cJCaLgB%2FtZP8kTaO4DKtLKrYZFTKa0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1696&quot; height=&quot;1164&quot; data-filename=&quot;2023-03-10_4.32.59.png&quot; data-origin-width=&quot;1696&quot; data-origin-height=&quot;1164&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;와!!!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 3등이었던 팀인 NobodyCares가 히든 데이터에서 터져 6등으로 내려가고, 우리는 하나도 안 터져서 3등으로 올라왔다!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 상금인 1000유로를 받고 세 명이 나눠 333유로씩 챙겨가면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글 쓰는 현재 시점의 가치론 각자 46.8만원씩 받아가는 셈인데, 난 사실상 3시간동안 이상한 코드만 짜고 놀았으니 놀면서 시급 15만원을 번 셈이 아닐까? 아니면 말고...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팀원 둘 다 PS를 잘 하는 친구들이라 버스를 타는 게 아닐까 걱정했는데 다행히 내가 충분히 기여를 했고, 결과가 좋아서 만족스럽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내년에 한 번 더 Teen에 참가해서 날먹하고 싶지만 조만간 만으로 20대에 접어드는 늙은이라 참가가 불가능하다 흑흑&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Standard에 참가해볼까? 싶은데 아무래도 Teen에 비해 훨씬 경쟁이 아주 치열해 보여서 참가한다고 해도 재미로만 할 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재밌는 대회 열어준 Reply Challenges에게 고맙다는 말을 남기며 글을 마친다. ;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>cgiosy</author>
      <guid isPermaLink="true">https://cgiosy.tistory.com/26</guid>
      <comments>https://cgiosy.tistory.com/entry/Reply-Code-Challenge-2023-Teen-Edition#entry26comment</comments>
      <pubDate>Sun, 12 Mar 2023 00:11:12 +0900</pubDate>
    </item>
    <item>
      <title>JS에서 꼬리재귀 최적화하기</title>
      <link>https://cgiosy.tistory.com/entry/JS%EC%97%90%EC%84%9C-%EA%BC%AC%EB%A6%AC%EC%9E%AC%EA%B7%80-%EC%B5%9C%EC%A0%81%ED%99%94%ED%95%98%EA%B8%B0</link>
      <description>&lt;p&gt;함수형 프로그래밍에서 반복문 대신 재귀를 쓸 때, 꼬리재귀 최적화가 없다면 메모리나 속도가 망하기 쉽다. 재귀 깊이 제한이 있다면 함수가 뻥뻥 터지기도 한다. 반복문으로 바꾸면 된다지만 썩 맘에 들지 않는 건 어쩔 수 없다.&lt;/p&gt;
&lt;p&gt;함수형을 맛보기 좋은 걸로 알려진 JS에(정확히는 가장 널리 쓰이는 JS 엔진인 V8에) 이런 꼬리재귀 최적화가 없다는 건 상당히 이율배반적인 소리로 들린다. 때문에, 적절한 규격을 가진 재귀함수를 내부적으로 반복문처럼 처리하는 유틸리티 함수를 만들어볼 수 있다.&lt;/p&gt;
&lt;p&gt;목표는 &lt;code&gt;T(self =&amp;gt; (n, acc = 0) =&amp;gt; (n ? self(n - 1, acc + n) : acc))(100000000)&lt;/code&gt;을 했을 때 콜스택이 터지지 않고 잘 돌아가도록 &lt;code&gt;T&lt;/code&gt; 함수를 만드는 것이다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const T = f =&amp;gt; (...args) =&amp;gt; {
    let end = true;
    const g = f((...newArgs) =&amp;gt; {
        args = newArgs;
        end = false;
    });
    while (true) {
        const ret = g(...args);
        if (end) return ret;
        end = true;
    }
};

T(self =&amp;gt; (n, acc = 0) =&amp;gt; (n ? self(n - 1, acc + n) : acc))(100000000)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;f&lt;/code&gt;에 함수 호출 여부와 다음에 쓸 인자를 확인하는 함수를 끼워넣어 준다. 이후 함수 호출이 멈출 때까지, 즉 재귀가 멈출 때까지 직전에 받은 인자를 넣어주며 반복한다.&lt;/p&gt;
&lt;p&gt;기능적으로는 콜스택이 깊어지는 게 방지돼서 터질 일은 줄어들지만, 속도는 오히려 일반 꼬리재귀함수에 비해서도 10% ~ 20%정도 느리다.&lt;/p&gt;
&lt;p&gt;아래처럼 spread 연산을 없애고 인자를 객체를 넘기는 방식으로 하면 일반 재귀에 비해 5배쯤 빠르긴 하지만, 여전히 반복문에 비해 5배쯤 느리다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const T = f =&amp;gt; (args) =&amp;gt; {
    let end = true;
    const g = f((newArgs) =&amp;gt; {
        args = newArgs;
        end = false;
    });
    while (true) {
        const ret = g(args);
        if (end) return ret;
        end = true;
    }
};

T(self =&amp;gt; ({ n, acc = 0 }) =&amp;gt; (n ? self({ n: n - 1, acc: acc + n }) : acc))({ n: 100000000 })&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;아쉽긴 하지만 최적화할 여지가 별로 안 보여서 대충 이정도로 마무리하는 걸로...&lt;/p&gt;</description>
      <author>cgiosy</author>
      <guid isPermaLink="true">https://cgiosy.tistory.com/24</guid>
      <comments>https://cgiosy.tistory.com/entry/JS%EC%97%90%EC%84%9C-%EA%BC%AC%EB%A6%AC%EC%9E%AC%EA%B7%80-%EC%B5%9C%EC%A0%81%ED%99%94%ED%95%98%EA%B8%B0#entry24comment</comments>
      <pubDate>Sun, 6 Nov 2022 03:06:40 +0900</pubDate>
    </item>
    <item>
      <title>빠른 해시 함수 JS로 포팅하기</title>
      <link>https://cgiosy.tistory.com/entry/%EB%B9%A0%EB%A5%B8-%ED%95%B4%EC%8B%9C-%ED%95%A8%EC%88%98-JS%EB%A1%9C-%ED%8F%AC%ED%8C%85%ED%95%98%EA%B8%B0</link>
      <description>&lt;p&gt;&lt;a href=&quot;https://github.com/cgiosy/xxh32&quot;&gt;https://github.com/cgiosy/xxh32&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;JS 최적화 상식&lt;/h2&gt;
&lt;h3&gt;정수 vs. 실수&lt;/h3&gt;
&lt;p&gt;JS는 기본적으로는 정수와 실수 구분이 없다. 사실상 모든 연산이 64비트 실수형 위에서 돌아간다고 보면 편하다.&lt;/p&gt;
&lt;p&gt;그래도 비트 연산을 사용하면 32비트 정수라도 쓸 수 있게끔 되어 있다. 아래에서 두 번째가 첫 번째보다 두 배 가까이 빠르다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const N = 16384;
const arr = new Uint32Array(N);
for (let i = 0; i &amp;lt; N; i += 1) arr[i] = i;

let s = 0;
// Case #1
for (let i = 0; i &amp;lt; N; i += 1) s += arr[i] + 1;
// Case #2
for (let i = 0; i &amp;lt; N; i = i + 1 | 0) s = s + (arr[i] + 1 | 0) | 0;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;따라서, 성능이 중요한 부분엔 &lt;code&gt;| 0&lt;/code&gt; 같은 비트 연산을 덕지덕지 발라가며 짜야 한다. &lt;a href=&quot;https://rescript-lang.org/&quot;&gt;ReScript&lt;/a&gt;같은 일부 파생 언어가 &lt;a href=&quot;https://github.com/rescript-lang/rescript-compiler/blob/master/lib/js/belt_HashMap.js&quot;&gt;이런 최적화&lt;/a&gt;를 지원은 하는데, 완벽하진 않다.&lt;/p&gt;
&lt;h3&gt;signed vs. unsigned&lt;/h3&gt;
&lt;p&gt;또한 대부분의 비트 연산은 반환 값이 signed이며, 2의 보수 특성 상 unsigned와 동일한 값인 게 보장된다. 단, 우측 시프트 &lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt; (signed) / &lt;code&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/code&gt; (unsigned)만 빼고. &lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt;는 시프트 후 남는 상위 비트를 sign bit로 채우고, &lt;code&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/code&gt;는 0으로 채운다. 참고로 unsigned보다 signed 연산이 빠른데, 왠진 모르겠다.&lt;/p&gt;
&lt;p&gt;기본 비트 연산을 제외하고 자주 사용되는 연산으론 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul&quot;&gt;Math.imul&lt;/a&gt;이 있는데, 32비트 정수 두 개를 곱했을 때의 하위 32비트를 반환한다. 마찬가지로 입력에 signed를 넣든 unsigned를 넣든 둘 다 넣든 결과값은 signed이다. 하위 32비트라서 unsigned와 동일한 값인 게 보장된다. &lt;a href=&quot;https://stackoverflow.com/questions/14063599&quot;&gt;참고&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;해시 종류 선택&lt;/h2&gt;
&lt;p&gt;내가 생각한 요구사항을 중요한 순서대로 나열했다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;32비트 연산만 사용&lt;/li&gt;
&lt;li&gt;작은 코드 크기&lt;/li&gt;
&lt;li&gt;최대한 빠른 속도&lt;/li&gt;
&lt;li&gt;적당한 품질&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;1이 가장 중요한 이유는 물론 JS는 32비트 정수밖에 다루지 못하기 때문이다. 64비트 연산을 직접 시뮬레이션하는 식으로 구현은 가능하지만, 그 경우 2와 3을 어느정도 희생해야 했다. 하여튼 AES-NI같은 명령어셋은 커녕 32비트 곱셈 결과의 상위 32비트를 가져오는 연산(umulh)조차 JS에는 없기에, 쓸만한 해시 함수의 종류가 훨씬 줄어들었다.&lt;/p&gt;
&lt;p&gt;2가 3과 4보다 높은 이유는, 유명 프론트 라이브러리에서 &lt;code&gt;h = h * 33 ^ s[i]&lt;/code&gt;같은 구석기시대에나 나올 법한 해시 함수를 쓰는 충격적인 광경을 목도했기 때문이다. 품질이 낮고, 속도가 느려도 이쪽 생태계는 그냥 코드가 짧고 간단한 게 최고임을 깨달았다. 아무리 그래도 저건 좀 심한 것 같아서 이렇게 따로 해시를 짜게 되긴 했지만...&lt;/p&gt;
&lt;p&gt;우선은 주로 &lt;a href=&quot;https://github.com/rurban/smhasher&quot;&gt;SMHasher&lt;/a&gt; &lt;a href=&quot;https://rurban.github.io/smhasher/doc/phone.html&quot;&gt;#1&lt;/a&gt; &lt;a href=&quot;https://rurban.github.io/smhasher/doc/table.html&quot;&gt;#2&lt;/a&gt; &lt;a href=&quot;https://rurban.github.io/smhasher/doc/ryzen3.html&quot;&gt;#3&lt;/a&gt;와 &lt;a href=&quot;https://aras-p.info/blog/2016/08/09/More-Hash-Function-Tests/&quot;&gt;More Hash Function Tests&lt;/a&gt; 블로그를 참고해서, 최종적으로 xxHash32를 선택했다. 고려했던 후보들은 다음과 같다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/wangyi-fudan/wyhash/blob/master/wyhash32.h&quot;&gt;wyhash32&lt;/a&gt;는 꽤 괜찮아 보이고 코드가 간단하지만,  &lt;code&gt;_wymix32&lt;/code&gt;에서 64비트 연산을 요구해서 32비트로 시뮬레이션을 해야 하나 고민하다가 결국 보내주었다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/google/farmhash/blob/0d859a811870d10f53a594927d0d0b97573ad06d/src/farmhash.cc#L1071&quot;&gt;Farmhash32&lt;/a&gt;는 꽤 괜찮아 보였으나, SIMD가 없으면 성능이 떡락한다는 걸 보고 버렸다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/google/cityhash/blob/f5dc54147fcce12cefd16548c8e760d68ac04226/src/city.cc#L189&quot;&gt;Cityhash&lt;/a&gt;는 bswap이나 permute 등 JS에서 지원하지 않는 연산을 많이 사용해서 걸러졌다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/tkaitchuck/aHash&quot;&gt;aHash&lt;/a&gt;는 메인 알고리즘이 AES-NI 명령어셋이 필요하고, fallback 알고리즘 역시 64비트 연산이 필요해 JS로 포팅이 불가능했다. 이외에 &lt;a href=&quot;https://github.com/Cyan4973/xxHash/issues/478&quot;&gt;성능이 과장되었다는 주장&lt;/a&gt;이 존재하며 실제로 M1 맥북에서 테스트 시 문자열이 256바이트 이상으로 큰 경우에는 XXH3에 비해 느렸고, 작은 경우에도 큰 차이를 보이진 못했다. (내장 벤치마크가 &lt;a href=&quot;https://github.com/shepmaster/twox-hash&quot;&gt;twox-hash&lt;/a&gt; 기준이라 &lt;a href=&quot;https://github.com/cberner/xxh3&quot;&gt;xxh3&lt;/a&gt;로 바꾸고 테스트했다.)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/gzm55/hash-garage/blob/master/nmhash.h#L781&quot;&gt;nmhash32x&lt;/a&gt;는 꽤 괜찮아 보이는데 당시 찾아볼 때 눈에 안 띄어서 지나쳐버렸다(...) 나중에 한 번 짜볼 듯.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/avaneev/komihash&quot;&gt;komihash&lt;/a&gt;, &lt;a href=&quot;https://github.com/tommyettinger/waterhash/blob/master/waterhash.h&quot;&gt;waterhash&lt;/a&gt;, &lt;a href=&quot;https://github.com/jonmaiga/mx3&quot;&gt;mx3&lt;/a&gt;, &lt;a href=&quot;https://github.com/tinypeng/pengyhash&quot;&gt;pengyhash&lt;/a&gt; 등 코드도 간단하고 그럭저럭 빠른 함수들이 많았지만 전부 64비트 대상이었다. 근데 사실 64비트면 XXH3나 wyhash 쓰지 저걸 쓸 이유가 없다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;결국 선택지가 없어져서, SMHasher 기준 품질이 POOR인 게 좀 찝찝했지만 32비트 연산만 사용하고 코드도 작고 꽤 빠른 xxHash32를 선택했다. nmhash32x는... ㅠㅠ&lt;/p&gt;
&lt;h2&gt;구현&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/Cyan4973/xxHash/blob/43ea6fdf838782688716f6ad85792a39e7e38e9a/xxhash.h#L2148&quot;&gt;C++ 구현&lt;/a&gt;을 보고 열심히 짰다. 코드가 좀 정리가 안 되어있고 각종 최적화나 매크로 분기가 눈을 어지럽히는데, 딱 기본적인 부분만 보면 생각보다 짧다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;XXH_FORCE_INLINE XXH_PUREF xxh_u32
XXH32_endian_align(const xxh_u8* input, size_t len, xxh_u32 seed, XXH_alignment align)
{
    xxh_u32 h32;

    if (input==NULL) XXH_ASSERT(len == 0);

    if (len&amp;gt;=16) {
        const xxh_u8* const bEnd = input + len;
        const xxh_u8* const limit = bEnd - 15;
        xxh_u32 v1 = seed + XXH_PRIME32_1 + XXH_PRIME32_2;
        xxh_u32 v2 = seed + XXH_PRIME32_2;
        xxh_u32 v3 = seed + 0;
        xxh_u32 v4 = seed - XXH_PRIME32_1;

        do {
            v1 = XXH32_round(v1, XXH_get32bits(input)); input += 4;
            v2 = XXH32_round(v2, XXH_get32bits(input)); input += 4;
            v3 = XXH32_round(v3, XXH_get32bits(input)); input += 4;
            v4 = XXH32_round(v4, XXH_get32bits(input)); input += 4;
        } while (input &amp;lt; limit);

        h32 = XXH_rotl32(v1, 1)  + XXH_rotl32(v2, 7)
            + XXH_rotl32(v3, 12) + XXH_rotl32(v4, 18);
    } else {
        h32  = seed + XXH_PRIME32_5;
    }

    h32 += (xxh_u32)len;

    return XXH32_finalize(h32, input, len&amp;amp;15, align);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;대략 이정도. 그래서 구현은 생각보다 어렵지 않았다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const getUint32 = (arr, i) =&amp;gt; (
    arr[i] | arr[i + 1 | 0] &amp;lt;&amp;lt; 8 | arr[i + 2 | 0] &amp;lt;&amp;lt; 16 | arr[i + 3 | 0] &amp;lt;&amp;lt; 24
);

const rotl32 = (x, r) =&amp;gt; (x &amp;lt;&amp;lt; r) | (x &amp;gt;&amp;gt;&amp;gt; 32 - r);
const round1 = (v, x) =&amp;gt; {
    v = v + Math.imul(x, 0x85EBCA77) | 0;
    return Math.imul(v &amp;lt;&amp;lt; 13 | v &amp;gt;&amp;gt;&amp;gt; 19, 0x9E3779B1);
};
const round2 = (v, x) =&amp;gt; {
    v = v + Math.imul(x, 0xC2B2AE3D) | 0;
    return Math.imul(v &amp;lt;&amp;lt; 17 | v &amp;gt;&amp;gt;&amp;gt; 15, 0x27D4EB2F);
};
const round3 = (v, x) =&amp;gt; {
    v = v + Math.imul(x, 0x165667B1) | 0;
    return Math.imul(v &amp;lt;&amp;lt; 11 | v &amp;gt;&amp;gt;&amp;gt; 21, 0x9E3779B1);
};

const xxh32 = (buf, seed = 0) =&amp;gt; {
    seed |= 0;
    const len = buf.length | 0;
    let i = 0;
    let h = (seed + len | 0) + 0x165667B1 | 0;

    if (len &amp;lt; 16) {
        for (; (i + 3 | 0) &amp;lt; len; i = i + 4 | 0)
            h = round2(h, getUint32(buf, i));
    } else {
        let v0 = seed + 0x24234428 | 0;
        let v1 = seed + 0x85EBCA77 | 0;
        let v2 = seed;
        let v3 = seed - 0x9E3779B1 | 0;

        if (len &amp;lt; 256) {
            for (; (i + 15 | 0) &amp;lt; len; i = i + 16 | 0) {
                v0 = round1(v0, getUint32(buf, i + 0 | 0));
                v1 = round1(v1, getUint32(buf, i + 4 | 0));
                v2 = round1(v2, getUint32(buf, i + 8 | 0));
                v3 = round1(v3, getUint32(buf, i + 12 | 0));
            }

            h = (((rotl32(v0, 1) + rotl32(v1, 7) | 0) + rotl32(v2, 12) | 0) + rotl32(v3, 18) | 0) + len | 0;
            for (; (i + 3 | 0) &amp;lt; len; i = i + 4 | 0)
                h = round2(h, getUint32(buf, i));
        } else {
            const view = new DataView(buf.buffer);
            for (; (i + 15 | 0) &amp;lt; len; i = i + 16 | 0) {
                v0 = round1(v0, view.getUint32(i + 0 | 0, true));
                v1 = round1(v1, view.getUint32(i + 4 | 0, true));
                v2 = round1(v2, view.getUint32(i + 8 | 0, true));
                v3 = round1(v3, view.getUint32(i + 12 | 0, true));
            }

            h = (((rotl32(v0, 1) + rotl32(v1, 7) | 0) + rotl32(v2, 12) | 0) + rotl32(v3, 18) | 0) + len | 0;
            for (; (i + 3 | 0) &amp;lt; len; i = i + 4 | 0)
                h = round2(h, view.getUint32(i, true));
        }
    }

    for (; i &amp;lt; len; i = i + 1 | 0)
        h = round3(h, buf[i]);
    h = Math.imul(h ^ h &amp;gt;&amp;gt;&amp;gt; 15, 0x85EBCA77);
    h = Math.imul(h ^ h &amp;gt;&amp;gt;&amp;gt; 13, 0xC2B2AE3D);
    return (h ^ h &amp;gt;&amp;gt;&amp;gt; 16) &amp;gt;&amp;gt;&amp;gt; 0;
};&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;구현 포인트&lt;/h3&gt;
&lt;p&gt;아마 위 코드를 본 순간 &amp;#39;rotl32 함수 멀쩡히 만들어 놓고 왜 round에선 안 쓰냐&amp;#39;는 감상이 들지 않았을까 싶다. 이유는 &lt;a href=&quot;https://docs.google.com/document/d/1VoYBhpDhJC4VlqMXCKvae-8IGuheBGxy32EOgC2LnT8/edit&quot;&gt;V8의 특이한 인라이닝 휴리스틱&lt;/a&gt;으로 인해, 동일 함수를 여러 함수에서 쓰면서 콜스택 깊이가 5를 넘는 경우 인라이닝이 제대로 안 될 수 있다. 후자의 조건은 현실에선 만족하기 쉬운데, 벤치마크 환경에선 잘 만족을 안 해서 7배씩 느려졌다 빨라졌다 하는 걸 보고 한참 헤맸다...&lt;/p&gt;
&lt;p&gt;그 외에 따로 신경쓴 구현 포인트는 다음과 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;편집증적인 &lt;code&gt;| 0&lt;/code&gt; 사용: 솔직히 여긴 안 붙여도 되지 않을까? 하고 뗀 순간 약간씩이지만 확실하게 성능이 떨어졌다.&lt;/li&gt;
&lt;li&gt;길이가 256 미만일 때를 분리: &lt;code&gt;new DataView()&lt;/code&gt;의 비용이 생각보다 커서, 저 크기보다 작을 땐 &lt;code&gt;getUint32&lt;/code&gt;가 좀 더 느려지는 걸 감수하고 &lt;code&gt;DataView&lt;/code&gt;를 생성 안 하는 게 나았다.&lt;ul&gt;
&lt;li&gt;&lt;code&gt;new TypedArray(otherTyped.buffer, offset)&lt;/code&gt;도 시도해 보았는데, 엄청나게 느려졌었다.(offset이 0이거나 4의 배수여도 관계없이)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;함수를 반환하는 함수 미사용: 일반 함수 선언까지는 어느정도 인라이닝이 되는데, 코드 중복을 없애려고 추상화를 시도한 순간 성능이 떨어졌다. 위에서 말한 인라이닝 문제와 어느정도 중복.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;비교&lt;/h3&gt;
&lt;p&gt;내 코드와 웹어셈블리 해시 라이브러리 중 가장 빠른 것으로 보이는 &lt;a href=&quot;https://github.com/Daninet/hash-wasm&quot;&gt;hash-wasm&lt;/a&gt;을 M1 맥북에서 벤치마크했다. 또한 해당 라이브러리에서 xxHash32 파트의 번들 크기는 gzip 압축 시 3kb, 내 코드는 0.5kb다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Throughput - 16 bytes
xxh32.js: 503.013 MB/s
hash-wasm.xxhash32: 40.957 MB/s
hash-wasm.xxhash64: 20.713 MB/s
hash-wasm.xxhash3: 19.955 MB/s
hash-wasm.crc32: 36.426 MB/s
hash-wasm.blake3: 15.988 MB/s
hash-wasm.sha256: 14.345 MB/s
hash-wasm.sha512: 9.491 MB/s
hash-wasm.sha3: 9.532 MB/s

# Throughput - 256 bytes
xxh32.js: 1522.584 MB/s
hash-wasm.xxhash32: 640.131 MB/s
hash-wasm.xxhash64: 340.317 MB/s
hash-wasm.xxhash3: 316.822 MB/s
hash-wasm.crc32: 490.829 MB/s
hash-wasm.blake3: 211.076 MB/s
hash-wasm.sha256: 138.229 MB/s
hash-wasm.sha512: 118.164 MB/s
hash-wasm.sha3: 107.931 MB/s

# Throughput - 4096 bytes
xxh32.js: 5593.867 MB/s
hash-wasm.xxhash32: 4032.488 MB/s
hash-wasm.xxhash64: 3859.344 MB/s
hash-wasm.xxhash3: 3155.710 MB/s
hash-wasm.crc32: 1571.842 MB/s
hash-wasm.blake3: 688.633 MB/s
hash-wasm.sha256: 301.666 MB/s
hash-wasm.sha512: 432.367 MB/s
hash-wasm.sha3: 286.119 MB/s

# Throughput - 65536 bytes
xxh32.js: 6725.478 MB/s
hash-wasm.xxhash32: 5521.466 MB/s
hash-wasm.xxhash64: 9151.555 MB/s
hash-wasm.xxhash3: 6397.267 MB/s
hash-wasm.crc32: 1779.452 MB/s
hash-wasm.blake3: 760.854 MB/s
hash-wasm.sha256: 320.870 MB/s
hash-wasm.sha512: 506.873 MB/s
hash-wasm.sha3: 309.703 MB/s&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;작은 크기에서 속도 차이가 크게 나는 건 WASM과 JS 간 데이터 전달로 인한 병목으로 추측되고, 입력이 꽤 큰 경우에는 그래도 64비트 연산을 사용 가능한 &lt;code&gt;hash-wasm.xxhash64&lt;/code&gt;이 크게 빨라진다.&lt;/p&gt;</description>
      <author>cgiosy</author>
      <guid isPermaLink="true">https://cgiosy.tistory.com/23</guid>
      <comments>https://cgiosy.tistory.com/entry/%EB%B9%A0%EB%A5%B8-%ED%95%B4%EC%8B%9C-%ED%95%A8%EC%88%98-JS%EB%A1%9C-%ED%8F%AC%ED%8C%85%ED%95%98%EA%B8%B0#entry23comment</comments>
      <pubDate>Tue, 11 Oct 2022 11:02:30 +0900</pubDate>
    </item>
    <item>
      <title>UCPC 2022 본선 후기</title>
      <link>https://cgiosy.tistory.com/entry/UCPC-2022-%EB%B3%B8%EC%84%A0-%ED%9B%84%EA%B8%B0</link>
      <description>&lt;p&gt;처음으로 참가한 대학생 대회이자, 첫 팀 대회이자, 다른 PS러를 자의로(ㅋㅋ) 만난 첫 대회이다. 대회 직후에 바로 후기 써야지 했는데 힘들어서 다음 날로 미루고, 또 미루다가 이제서야 쓴다 ㅎㅎ;&lt;/p&gt;
&lt;p&gt;기본적인 팀 설명 등은 지난 예선 후기에 작성했기 때문에 생략한다. 팀원인 &lt;a href=&quot;https://blog.naver.com/kyo20111/222830341700&quot;&gt;주때의 후기&lt;/a&gt;도 함께 보면 좋을 듯.&lt;/p&gt;
&lt;h3&gt;팀노트&lt;/h3&gt;
&lt;p&gt;본선 대회를 하루 남겨둔 저녁, 위기감을 느낀 우리는 어떻게든 팀노트를 만들기 시작했다.&lt;/p&gt;
&lt;p&gt;코드를 전부 우리가 다 짰으면 좋았겠지만, 팀노트는 한 페이지도 완성 안 돼있고 대회가 코앞인 상황에 그럴 시간은 없었다. 일단 예전에 짜둔 내 코드 30%, 주때의 작년 팀인 Longest path to WF의 팀노트, &lt;a href=&quot;https://github.com/kth-competitive-programming/kactl&quot;&gt;kactl&lt;/a&gt;에서 코드를 열심히 수혈받아왔다.&lt;/p&gt;
&lt;p&gt;특히 출제자 목록을 보고 플로우는 꼭 넣어야겠다고 생각했고, HLPP와 디닉을 테스트해본 뒤 둘 다 넣었다.&lt;/p&gt;
&lt;p&gt;그 외에 이것저것 짧은 시간동안 굉장히 많은 노력을 기울였다. 공백 고치고, 필요없는 / 필요할 것 같은 알고리즘 넣고, Fast IO 넣고, 모현한테 수학 라이브러리 좀 들고 오라고 갈구고, 주때 프린터 고장나서 모현한테 패스하는 거 구경하고, 기타 등등...&lt;/p&gt;
&lt;p&gt;...그러나 이 팀노트가 쓰이는 일은 없었다. 본선에서 반드시 쓰일 것이라 생각한 플로우 코드는 볼 일조차 없었고, 거짓말처럼 팀노트 필요없는 문제들 위주로 나오며 나는 폭사했다.&lt;/p&gt;
&lt;h3&gt;본선 전&lt;/h3&gt;
&lt;p&gt;팀노트를 대충 마무리하고 대략 오전 1시. 지방에 거주 중인 나는 잠깐 눈을 붙이고 오전 6시에 깨서 택시 - SRT - 택시로 10시 30분까지 대회장에 도착해야 했다.&lt;/p&gt;
&lt;p&gt;그리고, 대회 전날만 되면 긴장해서 잠을 못 자는 버릇이 또! 또!! 또!!! 도지고 말았다. 한 4시간쯤 정신은 말똥말똥한 채 눈만 감고 있는 끔찍한 경험은 작년과 재작년과 재재작년과 재재재작년만으로 충분하지 않았을까? 않았나보다...&lt;/p&gt;
&lt;p&gt;빈사 상태로 비척거리며 어떻게든 택시, SRT를 타고 수서역에 도착했다. 이제 다시 택시를 타서 기사님한테 &amp;#39;삼성역 근처 &lt;a href=&quot;https://map.naver.com/v5/entry/place/1549701937?placePath=%2Fhome&amp;amp;c=14144616.6126521,4510570.3637995,17.12,0,0,0,dh&quot;&gt;스페이스쉐어 삼성COEX센터&lt;/a&gt;&amp;#39;에 가달라고 말했다. 기사님은 알겠다고 말하며 바로 앞에 세워주겠다고 했기에 난 아무런 부담 없이 기다리다 내린 다음 &lt;strong&gt;코엑스&lt;/strong&gt;를 들어갔다.&lt;/p&gt;
&lt;p&gt;...근데 뭔가 이상했다. 분명 지하로 내려가면 바로 보일 거라고 했는데, 광활한 코엑스의 면적만 체감할 수 있었다. 불안함이 엄습해 미아가 된 채 카톡으로 헬프 콜을 쳤다. 그러고서야 깨달았다.&lt;/p&gt;
&lt;p&gt;삼성역 근처엔... 코엑스 어쩌구가 두 개 이상 있었던 것이다...!!!!&lt;/p&gt;
&lt;p&gt;깨닫자마자 헐레벌떡 코엑스(진)에서 탈출하고 코엑스(유사)로 허겁지겁 달려갔다. 길을 헤매서 한 5분 뛰면 될 걸 서너 배쯤 더 걸린 건 덤이다. 헷갈리게 삼성COEX센터라고 적어두지 말고 그냥 대화빌딩이라고 써주면 안 됐던 걸까 ㅠㅠ;;&lt;/p&gt;
&lt;p&gt;들어가기 전 정말 여기가 맞는 걸까? 사실 제3의 코엑스가 있는 건 아닐까? 하고 살짝 망설이다가, 다행히 다른 팀들도 쑥쑥 들어가길래 따라들어갔다. 엘리베이터를 타고 지하로 내려가니 사람들이 북적북적했다.&lt;/p&gt;
&lt;p&gt;그리고 마침내 3 ~ 4년째 나를 채팅으로만 알던 사람들과 만났다! AI인줄 알았는데 얼굴이 존재해서 이상하다는(?) 반응이 많았다. 그리고 난 죽기 일보 직전 + 오랜 집돌이 생활로 궤멸된 사회력으로 인해 말도 제대로 안 나왔다 ㅋㅋ..&lt;/p&gt;
&lt;p&gt;아주 대충 인사를 나눈 뒤 신분증 검사를 하고 자리에 가니 병아리같은 노란색 UCPC 티셔츠가 있었다. 화장실 가서 갈아입고 자리에 앉아 천천히 대회장을 둘러보는데, 솔직히 좀 당황스러웠다. 우리 자리 앞이 예선 1등이자 본선 1등 유력 후보란 것에 놀란 건 아니고, 테이블이 진짜 이게 맞나 싶을 정도로 가까웠다. 맞은편 팀과 거리 1.5m로 마주보며 대회를 치른다는 게 믿겨지는가? 난 좀 안 믿겼다.&lt;/p&gt;
&lt;p&gt;그렇게 멍때리며 주때가 들고 온 노트북 세팅이나 살짝씩 확인하는데, 노트북 키보드가 생각보다 되게 불편했다. 그래서 대회 중에 풀이 나오는 족족 코딩은 주때에게 짬때려야지 하고 다짐했지만, 헛된 꿈이었음을 지금 와서 깨닫는다...&lt;/p&gt;
&lt;p&gt;가만히 의자에 앉아 있으니 뉴페이스인 나나 바로 맞은편의 PS 슈퍼스타 구사과님 팀을 만나러 여러 사람이 방문했다. 정신이 없어서 정확히 누구누구 봤는진 기억이 안 나는데, 일단 리즈서, 돌, 카루나, 준서, 재원, 조영욱 정도가 기억이 난다. 아마 nlog(?)님도 오셨던 것 같은데 그냥 뇌정지와서 어버버했던 기억이... 뭔가 기대하고 오신 모든 분들께 그저 미안함만 들었다 ㅠㅠ 너무 아싸 티를 사방팔방으로 뿜어댐 흑흑&lt;/p&gt;
&lt;p&gt;류트는 본인이 누군지 알아보겠냐고 하길래 류트 아니냐고 답하니까 과연 코럴까요? 라면서 자꾸 그러니까 헷갈려서 설마 휘인가...? 하고 있는데 슥 가더라. 정작 휘는 머리가 너무 긴데다 마스크 껴서 찾아왔는데 순간 못 알아봤다 ㅋㅋ;&lt;/p&gt;
&lt;p&gt;암튼 그렇게 있다가 시간 되니까 좀 뜬금없이 대회가 시작됐다.&lt;/p&gt;
&lt;h3&gt;본선 대회 중&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://www.acmicpc.net/contest/spotboard/828&quot;&gt;스코어보드&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;어쩌다 보니 난 ABCD, 모현과 주때가 나머지 8문제로 분배됐다.&lt;/p&gt;
&lt;p&gt;일단 B는 딱 봐도 최후반부에야 한두 팀 이하로 풀 것 같아서 걸렀다. D는 딱 보니까 뭔가 식만 열심히 정리하면 자료구조로 쓱싹 가능한 것 같아서 호감이었다. C는 문제 분류가 애드혹 &amp;gt; 그리디 &amp;gt; 부분합 / DP 순으로 가능성이 있어 보였다. A는 DP를 어떻게 애드혹한 관찰을 섞어서 최적화해야 되는 것 같았다.&lt;/p&gt;
&lt;p&gt;C는 금방 떠오를 것 같은 문제는 아니라 A를 좀 찍먹해보기로 했다. 큰 거부터 &amp;#39;애드혹한 관찰&amp;#39;을 시도해 보는데 잘 안 됐다. 솔직히 C랑 A 둘 다 모현한테 던지고 싶었는데 이미 애드혹한 다른 문제를 푸는 중인 것 같아서 넘기기가 좀 그랬다...&lt;/p&gt;
&lt;p&gt;근데 앞쪽(1등팀)에서 자꾸만 풀이 얘기가 들려서 아무것도 안 들린다고 스스로 세뇌하느라 진땀을 뺐다. 다행히(?) ABCD 풀이 관련 얘기는 내 귀에 안 들렸는데 무슨 LR 플로우나 SoS DP 어쩌고 하는 건 아직도 기억에 남는다... 적어도 우리가 푼 문제 중엔 비슷한 게 없는 것 같은데 뭔 문젤까;&lt;/p&gt;
&lt;p&gt;아무튼 열심히 자기최면을 걸며 A를 보다 포기하고 D 식정리를 시도했다. 정리 난이도 자첸 쉬웠는데 머리 멍해서 실수 연발로 삽질만 1시간 넘게 한 것 같다. 그와중에 옆에 퍼솔 풍선 달리길래 봤는데 우리 팀이었다. 굉장해~&lt;/p&gt;
&lt;p&gt;일단은 식 대충 뽑고, 제일 구현 쉬운 레이지세그 풀이를 쓰고 I 디버깅 하던 주때를 밀어내고 코딩을 했다. 예제가 안 나왔다. 열심히 쳐다보니 C 예제랑 D 예제를 헷갈린 채로 풀고 있었다. 주때를 다시 앉히고 예시를 열심히 썼다.&lt;/p&gt;
&lt;p&gt;근데 아무리 봐도 식은 맞는데 직접 손으로 구해보면 예시 답이 안 나왔다. 잘 기억이 안 나는데 뭔가 굉장히 어리석은 실수를 했었다. 다시 코딩석으로 가서 열심히 짜고 맞았다. 키보드 불편해&lt;/p&gt;
&lt;p&gt;스코어보드를 보니 A는 노솔브였고 C가 그나마 풀만해 보였다. 보고 열심히 식을 정리한 끝에 부분합 다섯 개로 어떻게 풀이 비스무리한 게 나온 것 같았다. 근데 뭔가 이상해서 모현한테 이거 맞냐고 물어봤다. 잘 몰?루겠지만 맞다니까 맞겠지 같은 반응이 돌아왔다. 살짝 불안했지만 일단 짰다. 틀렸다.&lt;/p&gt;
&lt;p&gt;문제를 다시 읽었다. 정말 기본적인 관찰부터 굉장히 이상한 방향으로 엇나간 채 풀고 있었다. 수면 부족이 어디까지 사람을 뇌절하게 만들 수 있는 지 알 수 있는 시간이었다...&lt;/p&gt;
&lt;p&gt;영혼이 나간 채로 A를 봤는데, 수면부족 + 체력방전 + 멘탈터짐 삼중고가 쌓인 상태에서 잘 될 리가 만무했다. 주때와 열심히 살펴봤지만 개같이 멸망하고 난 1솔, 나머지 두 사람이 6솔 합계 7솔로 끝났다.&lt;/p&gt;
&lt;h3&gt;본선 대회 후&lt;/h3&gt;
&lt;p&gt;ABCD에서 A B C 셋 다 스코어보드 기준으로 어려운 문제였고, 컨디션이 망한 상태라 D밖에 풀지 못했다. 고 스스로 위안을 삼고 있긴 한데, 어찌 되었든 버스 타겠다곤 했어도 진짜로 버스를 타버리니 기분이 좀 그랬다.&lt;/p&gt;
&lt;p&gt;뭐 그건 그거고 최종적으로 우리 팀은 12등을 해서 5등상을 받게 되었다. 주때가 5등상 상품인 USB 허브를 받을 거면 차라리 퍼솔 상품이 더 받고싶었다고 하던 게 기억에 남는다. 근데 진짜 A나 C 뇌절만 덜 했어도 등수가 크게 달라질 수 있었을텐데 조금 아쉬운 마음도 든다.&lt;/p&gt;
&lt;p&gt;단체사진을 찍고 지인들 총 16명이 모여서 지하철을 타고 링고(치킨집)에 갔다. 대회장에서 나올 때 비가 오는데 우산이 없어 당황했는데, 주위를 보니 몇몇 사람이 상 받을 때 주는 팻말을 우산 대용으로(...) 쓰더라. 꽤 창의적인 방안이라 당장 채택하려고 우리 팀 팻말을 찾았는데 없더라...? 분명 내가 들고 있었는데 아차 화장실에 놔두고 왔던 거였다!&lt;/p&gt;
&lt;p&gt;아침에도 그랬듯이 또 헐레벌떡 뛰어가서 들고 와 주때와 쓰고 지하철로 갔다. 딱 둘이 비좁게 쓸 정도 크기더라. 모현은 잘 기억이 안 나는데 당당하게 비를 맞으면서 갔던가? 아마 우산이 있었던 것 같긴 하다.&lt;/p&gt;
&lt;p&gt;하여튼 설입에서 내리고 몇 분 걸으니, 드디어 가게에 도착했다. 그리 크진 않은 건물에 노란 티 입은 친구들이 우루루 들어오니 가게 주인분이 좀 당황하셨는데, 이정도 수의 손님은 가게 분위기 등의 이유로 잘 받지 않는다고. 근데 뭔가 잘 타협을 해서 테이블마다 네 명씩 나눠 앉게 됐다.&lt;/p&gt;
&lt;p&gt;내가 앉은 테이블엔 우리 팀(나, 모현, 주때)와 돌이 앉았다. 돌이 주도적으로 대회 얘기를 하며 진행되던 기억이 나는데, 놀랍게도 어지간한 주제엔 할 얘기가 없는 나조차 말이 나올 법한 대화였다. 근데 너무 피곤하기도 하고 집돌이 생활을 너무 오래 한 아싸라 그런지 입이 물리적으로 잘 안 열리더라 흑흑.. 역시 말보단 채팅이 편하다&lt;/p&gt;
&lt;p&gt;맥주나 술을 시키는 사람도 꽤 있었는데 난 딱히 마신 적이나 마실 생각이 없어서 오렌지 주스나 홀짝였다. 근데 무슨 한 컵에 수천 원씩 하더라... 그냥 들고 온 물이나 마시려고 했는데 주때가 자기가 테이블 사겠다며 그냥 시키라고 하길래 시켰다. 마셔 보니 집에 있는 델몬트가 더 맛있는 것 같아서 그냥 물이나 마실걸 하고 좀 후회했다.&lt;/p&gt;
&lt;p&gt;그렇게 시간을 좀 때우다가 SRT를 타야 해서 8시쯤에 먼저 자리에서 일어났다. 다른 사람들의 배웅을 받으며 다시 택시를 타고 SRT를 타고 버스를 타려니 버스가 끊겨서 또 택시를 탄 끝에 집에 도착했다. 뭔가 키보드 잡으면 여포지만 컴퓨터와 떨어지는 순간 전투력이 지하 내핵을 뚫고 들어가는 사람의 전형적인 모습을 유감없이 뽐낸 하루였었던 것 같다. 역시 사람이 안 하던 걸 하면 탈이 난다. 집이 최고...&lt;/p&gt;
&lt;p&gt;내 이슈로 여러모로 탈이 많던 대회였지만 그래도 나름 나쁘지 않은 경험이었다. 그치만 역시 온라인 대회가 더 좋다 ㅎㅎ&lt;/p&gt;
&lt;p&gt;뭐라고 끝맺을지 모르겠다. UCPC 화이팅!&lt;/p&gt;</description>
      <author>cgiosy</author>
      <guid isPermaLink="true">https://cgiosy.tistory.com/19</guid>
      <comments>https://cgiosy.tistory.com/entry/UCPC-2022-%EB%B3%B8%EC%84%A0-%ED%9B%84%EA%B8%B0#entry19comment</comments>
      <pubDate>Thu, 4 Aug 2022 23:25:06 +0900</pubDate>
    </item>
    <item>
      <title>Hash Table, Open Addressing, Robin Hood Hashing</title>
      <link>https://cgiosy.tistory.com/entry/Hash-Table-Open-Addressing-Robin-Hood-Hashing</link>
      <description>&lt;p&gt;아래에서 $N$은 테이블에 저장된 키 개수, $M$은 테이블 크기를 의미한다.&lt;/p&gt;
&lt;p&gt;$N$개의 키에 대한 해시값이 모두 $M$ 미만이고 distinct하다면, 즉 해시가 Perfect Hash Function이면 단순히 크기 $M$의 배열 각각에 키에 대한 값을 저장하면 된다.&lt;br&gt;Perfect Hash Function을 사용하는 해시 테이블은 보통 &lt;a href=&quot;https://en.wikipedia.org/wiki/Static_hashing#FKS_Hashing&quot;&gt;FKS 해싱&lt;/a&gt; 기반인데, 이에 대해선 여기서 설명하지 않겠다.&lt;/p&gt;
&lt;p&gt;그보단 대체로 &lt;a href=&quot;https://github.com/Cyan4973/xxHash&quot;&gt;xxHash&lt;/a&gt;나 &lt;a href=&quot;https://github.com/wangyi-fudan/wyhash&quot;&gt;wyhash&lt;/a&gt;처럼 매우 빠르며 충분히 랜덤함을 보장하는 해시 함수를 쓰게 된다. 문자열의 특정 부분만 뽑아 쓰는 해시도 &lt;a href=&quot;https://github.com/rurban/smhasher/blob/master/o1hash.h&quot;&gt;있긴 한데&lt;/a&gt;, 이 글에선 해시 함수가 서로 다른 키에 대해 완전히 랜덤한 출력을 보장함을 가정한다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Birthday_problem&quot;&gt;생일 문제&lt;/a&gt;에 의해, $N = \sqrt{M}$ 정도만 돼도 테이블에서 동일 해시값을 가진 두 키가 있을 확률이 50%나 된다. 물론 M을 늘릴 수록 저 확률은 감소하지만, 0이 될 수도 없고 메모리엔 제한이 있다.&lt;br&gt;때문에 제한된 $M$에서 최소한의 작업으로 동일한 해시값을 가진 키들을 구분하는 방법이 필요하다. 이런 방법 중 유명하고 간단한 방법들을 소개하겠다.&lt;/p&gt;
&lt;h4&gt;Chaining&lt;/h4&gt;
&lt;p&gt;동일한 해시 키들은 리스트나 트리에 넣는다.&lt;br&gt;탐색 시, 해당 해시 값의 위치에 저장된 키 목록을 나이브하게 탐색한다.&lt;/p&gt;
&lt;p&gt;평균 $O(\frac{N}{M})$ 정도의 중복이 발생하는데, $N = O(M)$이도록 유지할 수 있으면 탐색이 평균 $O(1)$인 셈이다.&lt;/p&gt;
&lt;p&gt;C++의 &lt;code&gt;std::unorderd_map&lt;/code&gt;은 &lt;a href=&quot;https://stackoverflow.com/questions/21518704/how-does-c-stl-unordered-map-resolve-collisions&quot;&gt;명세에 체이닝이어야 함이 반쯤 명시되어 있다&lt;/a&gt;. GCC 확장으로 &lt;code&gt;cc_hash_table&lt;/code&gt;과 &lt;code&gt;gp_hash_table&lt;/code&gt;이 있다. 커스텀 해시 함수가 없다면, 셋 모두 어렵지 않게 &lt;a href=&quot;https://codeforces.com/blog/entry/62393&quot;&gt;적대적 입력을 생성&lt;/a&gt; 가능하다.&lt;/p&gt;
&lt;p&gt;Java의 &lt;code&gt;HashMap&lt;/code&gt; 계열은 기본적으로 링크드 리스트로 체이닝을 하지만, 한 버킷에 데이터가 쏠려 있으면(적대적 입력) BBST로 전환해서 최악의 경우를 $O(\lg N)$으로 낮춘다.&lt;/p&gt;
&lt;h3&gt;Open Addressing&lt;/h3&gt;
&lt;p&gt;리스트나 vector를 달아서 체인을 처리하는 대신, 해시 테이블 배열 내에서 어떻게든 잘 처리하는 방식이다.&lt;/p&gt;
&lt;p&gt;아래에서 $L = \frac{M}{M - N}$이며, load factor $\lambda = \frac{N}{M}$에 대해 $\lambda = 1 - \frac{1}{L}$임을 참고하라.&lt;/p&gt;
&lt;h4&gt;Linear Probing&lt;/h4&gt;
&lt;p&gt;해시 값이 동일한 키들(체인)은 테이블에서 연속한 위치에 저장한다.&lt;br&gt;조회 시 우선 키의 해시 값을 구해 테이블에서 위치를 잡는다. 해당 위치에 저장된 키가 찾는 키와 동일하다면 해당 위치를 반환한다. 아니라면 위치를 $1$ 증가시킨 후 탐색을 반복하며, 체인의 끝에 도달하면 해당 키가 테이블에 없단 것이다.&lt;br&gt;삽입은 조회와 비슷한데, 체인의 끝에 키를 추가한다. 단, 탐색 중 동일한 키를 발견하면 추가할 키가 테이블에 이미 있단 것이다.&lt;/p&gt;
&lt;p&gt;삭제는 두 가지 방법이 있다. 하나는 삭제할 키를 체인의 끝으로 swap해서 보내고 체인 끝을 제거하는 것이고, 다른 하나는 그냥 해당 위치에 삭제 표시(tombstone)를 남기는 것이다. 둘 모두 체인이 끊어지지 &lt;strong&gt;않는다&lt;/strong&gt;.&lt;br&gt;tombstone이 있는 경우 삽입 시엔 빈 칸으로 해석하고 해당 위치에 키를 덮어쓴다. 조회 시엔 그냥 다음 칸으로 넘어간다.&lt;br&gt;좀 더 구체적인 내용은 저 아래 Robin Hood 해싱 부분에서 함께 설명한다.&lt;/p&gt;
&lt;p&gt;장점은 랜덤 액세스를 단 한 번만 해서 캐시 효율이 높다. 매우 직관적이고 짜기 쉽다. 삽입과 테이블에 있는 키 조회만 사용할 경우, 충분한 공간이 할당되면 제일 빠르다.&lt;br&gt;단점은 테이블에 없는 키 조회가 느리다. 키 제거가 섞이거나 테이블에 키가 가득 찰 수록 크게 느려진다. 이를 완화하기 위한 여러 방법이 존재하지만, 기본적인 Linear Probing의 소요 시간은 다음과 같다.&lt;/p&gt;
&lt;p&gt;삽입에 걸리는 총 시간은 $\Theta(NL)$이고, 이후 테이블에 없는 키 조회 및 삽입은 한 번 당 $\Theta(L^2)$이다.&lt;br&gt;예를 들어 테이블의 99%가 가득차 있는 경우, $L = 100$이므로 테이블에 없는 키 조회 및 삽입이 대략 $10000$배 느려지는 셈이다.&lt;/p&gt;
&lt;p&gt;이런 문제가 발생하는 이유는 긴 체인들끼리 이어질 경우 체인의 길이 분포가 더더욱 악화되기 때문이다.&lt;br&gt;다른 문제로 긴 체인은 테이블에서 차지하는 구간이 넓기 때문에, 삽입 시 긴 체인에 연결될 확률이 높고, 체인들의 길이가 고루 분포되지 않고 &lt;a href=&quot;https://en.wikipedia.org/wiki/Primary_clustering&quot;&gt;긴 게 더욱 길어지는 현상&lt;/a&gt;이 있기도 하다.&lt;/p&gt;
&lt;p&gt;가장 쉬운 해결법은 그냥 $M$을 늘리는 것이다. $N$이 $M$의 50% 이하이도록 잡는 게 좋다.&lt;br&gt;그러나 메모리가 그만큼 넉넉한 상황은 많지 않으며, 캐시 크기의 문제로 무작정 $M$을 늘리면 오히려 느려지는 경우도 발생한다. 하여튼 근본적인 해결책은 되지 않기에 $\Theta(L^2)$이란 끔찍한 복잡도를 낮추는 기술이 많다.&lt;/p&gt;
&lt;p&gt;가장 유명한 건 Quadratic Probing이다. 탐색 및 삽입 시 위치를 $1$씩 증가시키는 대신 $1$, $4$, $9$, $16$처럼 수를 제곱해가며 증가시킨다.&lt;br&gt;예상 탐색 횟수는 $0.5 + \frac{1}{2L} + \log L$이다. 하지만 캐시 지역성이 좀 희생되는 게 아쉬운 부분.&lt;/p&gt;
&lt;p&gt;여러 이유로 인해 삽입 시 reference invalidation을 허용하는 대신, 더 빠른 참조를 가능하게 하는 Robin Hood 휴리스틱이 있다.&lt;/p&gt;
&lt;h5&gt;Robin Hood Hashing&lt;/h5&gt;
&lt;p&gt;Linear Probing에서 테이블의 각 키에 대해 &amp;#39;원래 위치&amp;#39;와 &amp;#39;실제 위치&amp;#39; 간의 거리, 즉 해당 키를 조회하기 위해 필요한 탐색 수를 함께 저장해둔다.&lt;br&gt;삽입 시 다음 칸으로 넘어가기 전, 삽입할 키의 거리보다 지금 위치에 저장된 키의 거리가 작다면 둘을 swap한다. 이는 각 키의 거리를 최대한 차이나지 않게, 동시에 해시 값을 기준으로 &amp;#39;정렬된&amp;#39; 상태를 유지하는 것으로 해석 가능하다.&lt;/p&gt;
&lt;p&gt;덕분에 조회 시 지금 위치에 저장된 키의 거리 $d_{pos}$가 현재 탐색한 거리 $d$ 미만이면 테이블에 해당 키가 없음을 조기에 알 수 있다. 조회할 키의 해시가 $h$라 하면, 지금 위치에 저장된 키의 해시 값은 $h + d - d_{pos}$이고, $h &amp;lt; h + d - d_{pos}$면 뒤에도 전부 $h$보다 더 큰 해시를 가진 키만 존재하므로(정렬된 상태라) 더 볼 필요가 없기 때문이다.&lt;/p&gt;
&lt;p&gt;$L$이 $3$이나 $4$ 이상으로 좀 큰 경우, Organ Pipe Search란 기법을 적용하기도 한다. 이번엔 $d = d_{pos}$일 가능성이 높은 $d$부터 찍어보는 식이다. 방법은 그저 테이블에서 서로 다른 $d_{pos}$마다 개수를 세주고, 개수가 제일 많은 것부터 시도해보면 된다. 다른 테크닉과 잘 통합하며 깔끔하게 구현하긴 살짝 어렵다.&lt;/p&gt;
&lt;p&gt;해시 값을 기준으로 정렬되어 있단 점을 활용한 추가적인 활용법으로, 키가 이미 uniform하게 분포돼 있다면 상위 비트를 해시로 쓰고 범위 탐색(Range Search)도 가능하다. 또 이를 &lt;a href=&quot;https://github.com/mlochbaum/rhsort&quot;&gt;정렬에 활용할 수도&lt;/a&gt; 있다.&lt;/p&gt;
&lt;p&gt;아무것도 적용하지 않는 기본 Robin Hood의 경우, 조회에 걸리는 시간과 분산(variance)이 $O(L\ \mathrm{polylog}(L))$이라고 한다. $O(L^2)$에 비해 꽤나 개선이 있는 셈.&lt;/p&gt;
&lt;p&gt;삭제 기능은 &lt;a href=&quot;https://codecapsule.com/2013/11/17/robin-hood-hashing-backward-shift-deletion/&quot;&gt;시프트&lt;/a&gt;로 처리하는 게 대세였는데, 삽입/삭제가 반복되는 상황에서 특히 $L$에 영향을 많이 받는단 약점이 있다. 그래도 삽입/삭제를 번갈아 하지 않거나 $L$이 그리 크지 않은 대부분의 상황에서 매우 빠르다.&lt;/p&gt;
&lt;p&gt;최근에 나온 다른 방법으론 tombstone을 적극 활용해 조회에 걸리는 탐색을 항상 $O(L)$로 줄이는 &lt;a href=&quot;https://arxiv.org/pdf/2107.01250.pdf&quot;&gt;Graveyard Hashing&lt;/a&gt; 테크닉이 있다.&lt;br&gt;삽입/삭제를 특정 횟수로 했을 때마다 다음과 같이 테이블의 tombstone을 초기화한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;테이블의 tombstone을 &amp;#39;전부&amp;#39; 지운다.&lt;/li&gt;
&lt;li&gt;테이블에서 $i = 0, 1, 2, ...$에 대해 $i \times \frac{2M}{M-N}$ 위치에 새로 tombstone을 표시한다.&lt;/li&gt;
&lt;li&gt;이후 $\frac{M - N}{4}$번의 삽입/삭제 후에 초기화가 일어나도록 한다.&lt;br&gt;초기엔 tombstone이 존재하지 않으며, $\frac{M}{4}$번의 삽입/삭제 후에 초기화가 일어나야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;비교를 위해 테스트가 필요했다. &lt;a href=&quot;https://gist.github.com/cgiosy/dda34f4701482f1c6d07fb521d43325f&quot;&gt;내가 짠 Robin Hood 구현 및 간단한 테스트 코드&lt;/a&gt;. 딱 속도 테스트 용으로만 간단히 짠 거라, 실제 작동은 온전치 않을 수 있으므로 주의하라.&lt;/p&gt;
&lt;p&gt;키 조회는 평범한 랜덤과 현실에 좀 더 가까운 &lt;a href=&quot;https://en.wikipedia.org/wiki/Zipf%27s_law&quot;&gt;Zipf 분포&lt;/a&gt; 두 개로 각각 테스트해봤는데, 딱히 이걸로 상대적인 순위가 바뀌진 않았다. 둘 다 똑같은 Open Addressing 방식이니 당연한가? ㅋㅋ&lt;br&gt;키 $N$개 삽입 - 키 $N$개 제거를 반복하는 것보다 키 $N$개 삽입 후 각 키를 제거 후 즉시 추가를 반복하는 게 더 느렸다. 후자에서 $L \le 2$ 기준으로 $M$이 작을 땐 Graveyard 방식이, $M$이 클 땐 시프트 방식이 좀 더 빨랐다.&lt;br&gt;한 쪽이 그냥 압도했으면 선택하기 편했을텐데, 좀 짜증나는 결과...&lt;/p&gt;
&lt;p&gt;현재 범용적으로 가장 빠른 해시맵인 &lt;a href=&quot;https://github.com/skarupke/flat_hash_map/blob/master/bytell_hash_map.hpp&quot;&gt;bytell_hash_map&lt;/a&gt;에 비하면 $L = 2$에서 비슷하거나 조금 더 느리고, 테이블에 없는 키 조회는 두 배쯤 걸리기도 한다. 다만 키 삽입만큼은 어지간하면 두 배에서 세 배쯤 빠르다.&lt;br&gt;$L = 3$인 경우 키 조회는 $M$이 작을 경우 로직의 간결함으로 인해 크게 빠른 모습을 보여준다. $M$이 클 경우 RAM 레이턴시의 비중이 커져 차이가 줄어든다. 테이블에 없는 키 조회는 bytell과 비슷하거나 살짝 더 빠르다.&lt;/p&gt;
&lt;p&gt;정확히 몇% 차이나는지에 대해 언급을 피해 보기 답답할 수 있는데, 어쩔 수가 없는 게 CPU의 캐시와 RAM 레이턴시에 따라 결과의 차이가 너무 크다 ㅠㅠ...&lt;br&gt;M1 맥북의 경우 $M$이 작을 때 Graveyard가 시프트보다 키 조회가 3배쯤 빠른가 하면, 내 오라클 VPS에선 10% - 20%정도밖에 차이가 안 나는 등 상당히 골때린다;&lt;/p&gt;
&lt;p&gt;캐시 크기에 상관없이 항상 빠르도록 개선하는 방법은 없을까? 흠..&lt;/p&gt;
&lt;h4&gt;Swiss Table&lt;/h4&gt;
&lt;p&gt;(작성 중)&lt;/p&gt;
&lt;h3&gt;이외&lt;/h3&gt;
&lt;p&gt;Open Addressing 방식에는 위에 소개한 것들을 제외해도 &lt;a href=&quot;https://programming.guide/cuckoo-hashing.html&quot;&gt;Cuckoo Hashing&lt;/a&gt;이나 &lt;a href=&quot;https://programming.guide/hopscotch-hashing.html&quot;&gt;Hopscotch Hashing&lt;/a&gt;를 비롯한 여러 응용이 있다. 허나 그 중 대부분이 50% 이하의 낮은 load factor에서, 그리고 어쩌면 조금 높은 load factor에서도 Robin-Hood 해싱에 비해 유의미하게 빠르지 않았다.&lt;br&gt;때문에 실용적이면서, 실제로 꽤 사용되는 해시 테이블에 대해서만 서술하는 게 낫다고 생각해 해당 내용은 쓰려다가 쳐냈다.&lt;/p&gt;
&lt;h3&gt;구현 시 알아둘 점들&lt;/h3&gt;
&lt;p&gt;적대적 입력이 없는 경우, 해시 함수의 강도는 최소한의 퀄리티만 보장된다면 해시 테이블의 속도에 그리 영향을 주지 않는다. 나는 &lt;a href=&quot;https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/include/linux/hash.h&quot;&gt;피보나치 해싱&lt;/a&gt;이나 &lt;a href=&quot;https://github.com/wangyi-fudan/wyhash/blob/e77036ac1943369dc03e611cde52a8570f8ceefe/wyhash.h#L149&quot;&gt;랜덤 생성기&lt;/a&gt;를 가져와서 쓰고 있다.&lt;/p&gt;
&lt;p&gt;CPU마다 캐시 크기와 속도가 달라서, RAM 랜덤 액세스 레이턴시도 천차만별이다. 그래서 load factor같은 걸 적절히 잡기가 힘든데, 크게 잡으면 캐시 밖으로 나가서 속도가 크게 느려질 수 있기 때문이다.&lt;br&gt;&lt;a href=&quot;https://tessil.github.io/2016/08/29/benchmark-hopscotch-map.html&quot;&gt;벤치마크&lt;/a&gt;를 보면, 주로 삽입 테스트에서 &lt;code&gt;dense_hash_map&lt;/code&gt;과 &lt;code&gt;robin_map&lt;/code&gt;의 load factor가 $0.48$ -&amp;gt; $0.26$으로 전환되는 순간 시간이 크게 증가하는 것을 볼 수 있다.&lt;/p&gt;
&lt;h3&gt;References&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://dl.acm.org/doi/book/10.5555/1882757&quot;&gt;Algorithms and theory of computation handbook: general concepts and techniques&lt;/a&gt; - &lt;a href=&quot;https://dl.acm.org/doi/10.5555/1882757.1882759&quot;&gt;Searching&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://programming.guide/hash-tables.html&quot;&gt;Programming Guide - Hash Tables&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://doi.org/10.1017/S0963548318000408&quot;&gt;Analysis of Robin Hood and Other Hashing Algorithms Under the Random Probing Model, With and Without Deletions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://codecapsule.com/2013/11/17/robin-hood-hashing-backward-shift-deletion/&quot;&gt;Robin Hood hashing: backward shift deletion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>cgiosy</author>
      <guid isPermaLink="true">https://cgiosy.tistory.com/18</guid>
      <comments>https://cgiosy.tistory.com/entry/Hash-Table-Open-Addressing-Robin-Hood-Hashing#entry18comment</comments>
      <pubDate>Thu, 4 Aug 2022 00:16:12 +0900</pubDate>
    </item>
    <item>
      <title>다항식 기반 롤링 해시, 직접 만들어 써보기</title>
      <link>https://cgiosy.tistory.com/entry/%EB%8B%A4%ED%95%AD%EC%8B%9D-%EA%B8%B0%EB%B0%98-%EB%A1%A4%EB%A7%81-%ED%95%B4%EC%8B%9C-%EC%A7%81%EC%A0%91-%EB%A7%8C%EB%93%A4%EC%96%B4-%EC%8D%A8%EB%B3%B4%EA%B8%B0</link>
      <description>&lt;p&gt;어떤 집합에서 결합법칙을 만족하는 이항연산 $+$와 역연산 $-$가 있고, $f(x + y) = f(x) + f(y)$를 만족하는 함수 $f$가 있을 때, $f^n(x)$를 빠르게 계산할 수 있다면 길이 $N$의 문자열 $S$에 대해 $h = \sum_{k=1}^{N}{f^{N-k}(S[k])}$를 롤링 해시로 활용할 수 있다.&lt;/p&gt;
&lt;p&gt;$f(h) + S[N + 1]$로 뒤에 문자를 추가할 수 있고, $h - f^{N-1}(S[1])$로 앞에서 문자를 삭제할 수 있다. 당연히 중간에서 삭제 및 대체도 가능하지만, 앞뒤 추가 및 삭제에 비해 거의 안 쓰이는 편이다.&lt;/p&gt;
&lt;h3&gt;예시&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Rolling_hash&quot;&gt;위키피디아의 롤링 해시 문서&lt;/a&gt;에선 다음 두 가지 다항식 기반 롤링 해시를 소개한다.&lt;/p&gt;
&lt;h4&gt;다항식 롤링 해시&lt;/h4&gt;
&lt;p&gt;적당한 상수 $p$, $q$가 있을 때, modulo $p$에서 $f(x) = qx$인 경우다.&lt;br&gt;Rabin fingerprint랑은 다르다고 하는데, 비슷하게 생겨서 다들 이걸 Rabin fingerprint, Rabin-Karp 알고리즘이라고 부른다. 근데 솔직히 중요한 사항은 아닌 것 같다.&lt;/p&gt;
&lt;p&gt;롤링 해시라고 하면 십중팔구는 이를 가리키고, 그만큼 상당히 안정적인 품질을 보여준다.&lt;br&gt;허나 $p$를 $2^w$로 잡으면 &lt;a href=&quot;http://codeforces.com/blog/entry/4898&quot;&gt;적대적 입력에 매우 취약하다&lt;/a&gt;. $p$를 소수로 잡고 약간의 처리 시 꽤 완화 가능하나 속도와 구현 면에서 복잡성이 있다.&lt;/p&gt;
&lt;h3&gt;Cyclic Polynomial (Buzhash)&lt;/h3&gt;
&lt;p&gt;사용할 자료형의 비트수 $w$에 대해, $[0, 2^w)$에서 덧셈이 XOR, $f$가 ROTL(비트 왼쪽 회전)인 경우다.&lt;/p&gt;
&lt;p&gt;$w &amp;lt; N$이 되는 순간, 처음 문자가 한 바퀴를 완전히 돌게 돼 품질이 급격히 나빠지는 단점이 있어 $N$이 작은 경우에만 사용 가능하다.&lt;/p&gt;
&lt;h3&gt;해시 함수 직접 만들어 쓰기&lt;/h3&gt;
&lt;p&gt;원래는 덧셈과 뺄셈 기반의 해시를 변형해 쓰려 했으나, 수학적 지식이 부족해 곱셈 말곤 분배법칙을 만족하는 괜찮은 연산을 찾지 못했다. modulo 기반인데다 $p$, $q$같은 상수 정하기 까다로운 게 썩 마음에 안 들기도 하고...&lt;/p&gt;
&lt;p&gt;덧셈과 뺄셈 대신 XOR을 쓰는 경우, 고정된 순열 $P$를 적당히 잡고 해당 순서대로 비트를 섞는 연산을 $f$로 쓸 수 있다. Cyclic Polynomial 해시의 ROTL도 이에 속하는데, 주기가 $w$로 너무 짧은 게 문제였다.&lt;/p&gt;
&lt;p&gt;순열의 주기는 &lt;a href=&quot;https://en.wikipedia.org/wiki/Permutation#Definition&quot;&gt;사이클&lt;/a&gt;에 의존한다. 조금 고민해보면 각 사이클 길이의 $\mathrm{lcm}$임을 알 수 있다. 비트 회전은 사이클이 그냥 한 덩어리라 주기가 극히 짧았던 것...&lt;br&gt;$\mathrm{lcm}$ 값을 키우려면 사이클을 소수 기준으로 쪼개야 한다. 이렇게 해서 만들 수 있는 최댓값은 &lt;a href=&quot;https://oeis.org/A000793/b000793.txt&quot;&gt;OEIS&lt;/a&gt;에 나열되어 있는데, 길이 64에선 주기가 최대 $2^3 \times 3 \times 5 \times 7 \times 11 \times 13 \times 17 = 2042040$인 것을 볼 수 있다. 아주 크진 않지만, 기존보단 훨씬 큰 것을 알 수 있다.&lt;br&gt;또한 주기와 별도로, 사이클의 개수는 대략 $O(\sqrt{w})$ 혹은 그보다 작은 것으로 보인다.&lt;/p&gt;
&lt;h4&gt;구현&lt;/h4&gt;
&lt;p&gt;어떻게 쪼개야 주기가 최대가 되는진 알았으니, 실제 순열을 만들어야 한다. 가장 간단하게는 다음과 같이 할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1 2 3 4 5 6 7 0 9 10 8 12 13 14 15 11 17 18 19 20 21 22 16 24 25 26 27 28 29 30 31 32 33 23 35 36 37 38 39 40 41 42 43 44 45 46 34 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 47&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이는 우선 &lt;code&gt;x &amp;amp; 0x7FFFBFFDFFBF7B7F&lt;/code&gt;으로 왼쪽으로 한 칸 보낼 비트들을 추출한 뒤 &lt;code&gt;&amp;lt;&amp;lt; 1&lt;/code&gt;을 해주고, 나머지 &lt;code&gt;0x8000400200408480&lt;/code&gt;에 포함되는 비트들을 각각 &lt;code&gt;0x800400810901&lt;/code&gt;로 보내줘야 한다.&lt;br&gt;전자는 쉽지만, 후자를 효율적으로 처리하려면 &lt;a href=&quot;https://en.wikipedia.org/wiki/X86_Bit_manipulation_instruction_set&quot;&gt;인텔 BMI2 명령어셋&lt;/a&gt;의 PEXT와 PDEP 연산이 필요하다. 이를 활용한 구현은 다음과 같다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;using ull = unsigned long long;
const ull SHR = 0x8000400200408480, SHL = SHR &amp;lt;&amp;lt; 1 | SHR &amp;gt;&amp;gt; 63;
ull shl(ull x) { return _pdep_u64(_pext_u64(x, SHR), SHL) | (x &amp;amp; ~SHR) &amp;lt;&amp;lt; 1; }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제 $f^n(x)$, 그러니까 순열을 $n$번 적용한 값을 구해야 한다. 조금 느린 걸 감수하고 각 사이클을 독립적으로 고려하면, 그냥 &lt;code&gt;mod 사이클 길이&lt;/code&gt;를 한 다음 해당 비트를 뽑아서 잘 ROTL해주면 된다. 이를 단순하게 구현하면 다음과 같다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;template&amp;lt;ull m, ull k&amp;gt; ull rotl(ull x, ull n) {
    ull constexpr t = (1ULL &amp;lt;&amp;lt; m) - 1 &amp;lt;&amp;lt; k;
    x &amp;amp;= t; n %= m;
    return (x &amp;lt;&amp;lt; n | x &amp;gt;&amp;gt; m - n) &amp;amp; t;
}

ull shl(ull x, ull n) {
    return rotl&amp;lt;8, 0&amp;gt;(x, n) | rotl&amp;lt;3, 8&amp;gt;(x, n) | rotl&amp;lt;5, 11&amp;gt;(x, n) | rotl&amp;lt;7, 16&amp;gt;(x, n)
        | rotl&amp;lt;11, 23&amp;gt;(x, n) | rotl&amp;lt;13, 34&amp;gt;(x, n) | rotl&amp;lt;17, 47&amp;gt;(x, n);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제 비트 회전에 PEXT와 PDEP를 적용해보자. 이번엔 첫 &lt;code&gt;shl&lt;/code&gt; 함수 구현과는 달리 단일 시프트로 ROTL을 처리할 수 없다. 때문에 ROTL에 한 번, ROTR에 한 번 해서 각 연산을 두 번씩 쓰게 된다.&lt;br&gt;하지만 이 최적화를 해도 PEXT와 PDEP에 넣을 mask 처리가 굉장히 까다로워 실질적인 이득은 크지 않다.&lt;/p&gt;
&lt;p&gt;그렇다면 mask를 &lt;code&gt;n&lt;/code&gt;에 대해 전처리해두는 건 어떨까? 이 경우 직전인 &lt;code&gt;n - 1&lt;/code&gt;일 때의 결과를 활용 가능해서, modulo 연산이 필요치 않다. 코드로 보면 다음과 같다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;ull shr(ull x) { return _pdep_u64(_pext_u64(x, SHL), SHR) | (x &amp;amp; ~SHL) &amp;gt;&amp;gt; 1; }
ull shl(ull x, ull n) {
    static std::vector&amp;lt;std::pair&amp;lt;ull, ull&amp;gt;&amp;gt; P = { {} };
    while(P.size() &amp;lt;= n) {
        auto [ a, b ] = P.back();
        P.push_back({ shr(a) ^ SHR, shl(b) ^ SHL });
    }
    auto [ a, b ] = P[n];
    return _pdep_u64(_pext_u64(x, a), b) | _pdep_u64(_pext_u64(x, ~a), ~b);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;사실 그리 완전히 최적화된 구현은 아니라, 이를 그대로 적용하면 성능 향상이 미미하다. 그냥 예시로 봐주길 바라며, 내가 실제로 사용하는 코드는 글 끝에 첨부해두었다.&lt;/p&gt;
&lt;h4&gt;ARM&lt;/h4&gt;
&lt;p&gt;안타깝게도, 내 노트북 M1 맥북에선 이 코드가 돌아가지 않는다. BMI2 명령어셋이 없어 PEXT와 PDEP 연산이 돌아가지 않기 때문이다. 때문에 테스트해보려면 다음과 같은 코드를 작성해야 한다. (물론 매우 느리다!)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;ull _pext_u64(ull x, ull m) {
    ull n=0, y=0;
    for(ull i=0; i&amp;lt;64; i++) if(m&amp;gt;&amp;gt;i&amp;amp;1) y|=(x&amp;gt;&amp;gt;i&amp;amp;1)&amp;lt;&amp;lt;n++;
    return y;
}
ull _pdep_u64(ull x, ull m) {
    ull n=0, y=0;
    for(ull i=0; i&amp;lt;64; i++) if(m&amp;gt;&amp;gt;i&amp;amp;1) y|=(x&amp;gt;&amp;gt;n++&amp;amp;1)&amp;lt;&amp;lt;i;
    return y;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;최종 코드&lt;/h4&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/cgiosy/63757207a235095821ef9fb61fd4df3a&quot;&gt;https://gist.github.com/cgiosy/63757207a235095821ef9fb61fd4df3a&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;성능&lt;/h4&gt;
&lt;p&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1786&quot;&gt;단순 문자열 검색&lt;/a&gt;의 경우, $N = 10^6$ 정도에서 KMP보다 조금 느린 속도를 보여준다.&lt;br&gt;특이하게도 전처리를 할 경우, 별 차이가 없거나 기존보다 미세하게 느렸다. &lt;code&gt;shl&lt;/code&gt;을 $O(N)$번만 호출해서 그런 듯하다.&lt;/p&gt;
&lt;p&gt;해싱 + LCP 비교 기반 문자열 정렬은 문자열 길이의 합이 $M$일 때, 대략 $O(N \lg N \lg {\frac{M}{N}})$이 시간이 소요된다. 정렬에 기본적으로 $O(N \lg N)$이 소모되고, 각 비교 연산마다 LCP 계산이 필요하다. LCP는 이분탐색을 사용해 $O(\lg \min(|X|, |Y|))$에 구할 수 있고, 이를 정렬에 적용 시 해당 시간복잡도가 나온다.&lt;br&gt;즉 $M = O(N)$이면 $O(N \lg N)$, $M = O(N^2)$ 정도면 $O(N \lg^2 N)$이 됨을 알 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/9248&quot;&gt;접미사 배열&lt;/a&gt; 계산의 경우 $O(N \lg^2 N)$이며, 실성능 테스트 시 기존 접미사 배열 알고리즘의 $O(N)$ 구현체에 비해 6배정도, $O(N \lg N)$에 비해 2~3배정도 느리다. 일반 $O(N \lg^2 N)$ 접미사 배열 구현보단 좀 더 빠른 성능을 보여준다(!)&lt;br&gt;일반 정렬보다 &lt;code&gt;stable_sort&lt;/code&gt;가 30%정도 더 빠름에 주의하라.&lt;/p&gt;</description>
      <author>cgiosy</author>
      <guid isPermaLink="true">https://cgiosy.tistory.com/17</guid>
      <comments>https://cgiosy.tistory.com/entry/%EB%8B%A4%ED%95%AD%EC%8B%9D-%EA%B8%B0%EB%B0%98-%EB%A1%A4%EB%A7%81-%ED%95%B4%EC%8B%9C-%EC%A7%81%EC%A0%91-%EB%A7%8C%EB%93%A4%EC%96%B4-%EC%8D%A8%EB%B3%B4%EA%B8%B0#entry17comment</comments>
      <pubDate>Thu, 14 Jul 2022 07:58:00 +0900</pubDate>
    </item>
    <item>
      <title>UCPC 2022 예선 후기</title>
      <link>https://cgiosy.tistory.com/entry/UCPC-2022-%EC%98%88%EC%84%A0-%ED%9B%84%EA%B8%B0</link>
      <description>&lt;p&gt;처음으로 참가한 대학생 대회다.&lt;/p&gt;
&lt;p&gt;팀 구성은 4월 초쯤에 이뤄졌다. 내가 올해에 PS를 열심히 할 것 같지도 않고, 맥레 오렌지밖에 안 되는 평범한 양민 새내기가 빡겜팟을 찾는 건 에바같아서 지인들 채팅방에서 즐겜팟을 모집했다. 글을 올리고 금방 &lt;a href=&quot;https://www.acmicpc.net/user/cgiosy&quot;&gt;cgiosy&lt;/a&gt;, &lt;a href=&quot;https://www.acmicpc.net/user/ahgus89&quot;&gt;ahgus89&lt;/a&gt;, &lt;a href=&quot;https://www.acmicpc.net/user/kyo20111&quot;&gt;kyo20111&lt;/a&gt; 세 명으로 팀을 만들게 됐다. 각각 소개해 보자면,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;cgiosy&lt;/strong&gt;: 오렌지 주차시켜둔 퍼플 실력 / 웰노운이랑 이상한 걸 위주로 한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ahgus89&lt;/strong&gt;: 찐렌지에 가까운 오렌지 / 수학(특히 정수론)이랑 애드혹을 잘한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;kyo20111&lt;/strong&gt;: 그냥 레드 / 웰노운이랑 구현을 잘하는 것 같다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;생각보다 꽤 안정적인 팀이 갖춰져서 신기했다. 주때(kyo20111)는 개인대회에선 SCPC 2등 및 코포 레드, 팀대회에선 ICPC 및 UCPC 8등이란 멋진 실적이 있었다. 수학과 능지문제 담당인 모현(ahgus89)은, 출제자로 나올 땐 두렵지만 참가자로 같이 하면 꽤 든든할 것 같았다. 난 버스만 타고 있으면 될 것 같아서 기분이 좋았다.&lt;/p&gt;
&lt;h3&gt;예선 전&lt;/h3&gt;
&lt;p&gt;4월 중순쯤에 사전 수요조사가 있었는데, 난 본선 온라인 개최를 강력히 희망했으나 아쉽게도 본선은 오프라인으로 열리게 됐다.&lt;/p&gt;
&lt;p&gt;6월 초엔 어그로가 끌릴만한 팀명을 몇 개 생각하다가, 적당히 &amp;#39;숭실사이버의대를다니고나의성공시대시작됐다&amp;#39;로 정했다.&lt;/p&gt;
&lt;p&gt;예선 대회 직전인 6월 28~29일엔 팀연습을 하려고 했지만, 다들 기상 시간이 제각각이라 교집합이 하나도 없어 파토났다. 문제 보는 전략은 대충 주때 ABCD / 모현 EFGH / 나 IJKL 처럼 쪼개기로 했다.&lt;/p&gt;
&lt;p&gt;대망의 7월 2일까지도 팀연습따윈 없었기에, 우린 진짜 즐겜팟이 되었다. 난 그냥 인터넷 보면서 놀고 있었고, 다른 둘은 메이플 하다가 왔다. 보이스 켜란 말을 들었지만 난 말보다 채팅이 자신있었기에 굴하지 않고 듣기만 했다.&lt;/p&gt;
&lt;p&gt;곧이어 UCPC 예선이 시작됐다. 백준이 터졌다.&lt;/p&gt;
&lt;h3&gt;예선 중&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://ucpc.acmicpc.net/contest/spotboard/827&quot;&gt;스코어보드&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;0:02&lt;/strong&gt;: 주때가 빠르게 A를 제출하고 모현이 E F가 단순 구현임을 알렸다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;0:07&lt;/strong&gt;: H I J를 대충 훑었다.&lt;ul&gt;
&lt;li&gt;I는 딱 봐도 어려워 보였다.&lt;/li&gt;
&lt;li&gt;J는 좀 수학 + 애드혹스러웠다. 폴라드 로로 뇌절을 잠깐 하다가, 모현한테 주기로 했다.&lt;/li&gt;
&lt;li&gt;H는 좀 쉬워보였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;0:15&lt;/strong&gt;: 모현이 J를, 주때가 C를 짜기 시작했다. 난 H 풀이를 고민했다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;0:23&lt;/strong&gt;: 모현이 J를 맞았다. 역시 수학애드혹고수&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;0:24&lt;/strong&gt;: 모현이 E를 구현하기 시작했다. 난 H가 카탈란 수를 쓰는 것까지 접근했고, 스택으로 어떻게 구현할지 고민했다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;0:28&lt;/strong&gt;: 내가 H를 짜기 시작했다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;0:39&lt;/strong&gt;: 주때와 모현이 각각 C E를 한 번씩 틀리고, 내가 H를 맞았다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;0:41&lt;/strong&gt;: 주때가 냅색 관련 고민을 말했다 (아마 C 풀이 관련). 솔직히 잘 모르겠어서, 맞을듯? 하고 넘겼다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;0:42&lt;/strong&gt;: F를 잡으려다가, B도 많이 풀렸대서 B부터 잡았다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;0:50&lt;/strong&gt;: B는 살짝 뇌절하다가 많이 풀리는 걸 보고 쉽게 접근하기로 했다. 그냥 w 작은거부터 쓱 하면 될 것 같은 믿음을 가지고 짜기 시작했다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1:02&lt;/strong&gt;: 선분 교차 짜는 법을 까먹어서 구현에 꽤 애먹었다. 어쨌든 B를 맞았다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1:03&lt;/strong&gt;: 모현이 E에 9틀을 박고 죽어가는 중이었다. 내가 F를 잡으려다가, 모현이 F 잡고 내가 E를 다시 짜기로 했다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1:05&lt;/strong&gt;: 모현이 F를 짜기 시작했다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1:10&lt;/strong&gt;: 내가 E를 짜기 시작했다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1:18&lt;/strong&gt;: 모현이 F를 맞고 D로 넘어갔다. 주때가 C 맞왜틀의 늪에 빠지고 있었다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1:28&lt;/strong&gt;: 내가 E를 제출하고 틀렸다. 주때가 C를 맞았다. 스파스 테이블에서 i j를 헷갈려서 틀렸었다고 한다.&lt;ul&gt;
&lt;li&gt;대회 끝나고 보니 C가 다5였다. 말 없이 쓱쓱 풀길래 별 생각 없었는데 좀 쩌는듯&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1:37&lt;/strong&gt;: E를 한참 뚫어져라 보다가 고칠만한 게 없어 보여서 제출해서 맞고 I로 넘어갔다. 틀리면 패널티가 붙으니까 구현해도 제출하기가 무섭다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2:00&lt;/strong&gt;: 모현이 D 디버깅에 고통받고 있었다. 주때가 소리소문 없이 G를 맞아왔다. 난 I를 한참 보고 있는데 모르겠었다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2:12&lt;/strong&gt;: 모현이 D를 풀었다. 이걸로 셋 다 유일하게 남은 문제인 I를 붙잡게 됐다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2:30&lt;/strong&gt;: 한참 고민하다가 &lt;a href=&quot;https://www.acmicpc.net/problem/1659&quot;&gt;수 (Hard)&lt;/a&gt;랑 비슷한 느낌이 들어서 말했다. 동의는 못 받았다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2:34&lt;/strong&gt;: 주때가 같은 방향 선분 여러 개 겹쳐 있으면 하나로 합쳐도 된단 관찰을 말했다. 모현은 이해했는데 난 이해못했다.&lt;ul&gt;
&lt;li&gt;반대 방향 선분 있어서 문제라고 하길래 비용 항상 &lt;code&gt;|s-i| + |e-i| + |e-s|&lt;/code&gt;니까 상관없는 거 아니냐고 했다.&lt;/li&gt;
&lt;li&gt;주때가 예제를 보여줬다. 내가 예제 그림 제대로 안 보고 문제 잘못 이해한 상태로 푸는 중이었다 ㅡㅡ;;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2:40&lt;/strong&gt;: 주때가 뭔가 믿음을 기반으로 코딩을 시작했다. 모현이랑 나도 이상한 풀이 하나씩 짜고 있었다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2:59&lt;/strong&gt;: 주때가 I 제출 후 틀리고 사망했고, 모현은 코딩 중 사망했고, 난 맨하탄 MST 짠 다음 역추적 하려다가 사망했다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;3:00&lt;/strong&gt;: I를 제외하고 모두 푼 9솔브로 대회를 마감했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;예선 후&lt;/h3&gt;
&lt;p&gt;14등으로 매우 넉넉하게 본선 컷에 들었다. 패널티가 좀 많이 쌓여서 9솔 꼴찌일 거라고 예상했는데 아니더라 ㅎㅎ;&lt;/p&gt;
&lt;p&gt;각자 푼 문제를 정리하면, 다음과 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;cgiosy&lt;/strong&gt;: &lt;a href=&quot;https://www.acmicpc.net/problem/25321&quot;&gt;H (플3)&lt;/a&gt; → &lt;a href=&quot;https://www.acmicpc.net/problem/25315&quot;&gt;B (골2)&lt;/a&gt; → &lt;a href=&quot;https://www.acmicpc.net/problem/25318&quot;&gt;E (실1)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ahgus89&lt;/strong&gt;: &lt;a href=&quot;&quot;&gt;J (골1)&lt;/a&gt; → &lt;a href=&quot;https://www.acmicpc.net/problem/25319&quot;&gt;F (골5)&lt;/a&gt; → &lt;a href=&quot;https://www.acmicpc.net/problem/25317&quot;&gt;D (플4)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;kyo20111&lt;/strong&gt;: &lt;a href=&quot;https://www.acmicpc.net/problem/25314&quot;&gt;A (브5)&lt;/a&gt; → &lt;a href=&quot;https://www.acmicpc.net/problem/25316&quot;&gt;C (다5)&lt;/a&gt; → &lt;a href=&quot;https://www.acmicpc.net/problem/25320&quot;&gt;G (플5)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;딱 세 개씩 풀기도 했고, 난이도 배분이 꽤 잘 이뤄진 것 같아 만족스럽다. 아쉬운 점은 A 다음으로 쉬운 문제인 E와 F를 빠르게 풀었으면 어땠을까 싶다. 각자의 단점이라 느껴진 건:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;cgiosy&lt;/strong&gt;: 애드혹을 그냥 개못한다. 구현이 너무 느리다. 한 번 뇌절하면 끝까지 뇌절한다. 문제를 한 번에 제대로 못 읽는다. 나 혹시 난독증??&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ahgus89&lt;/strong&gt;: 구현하다 종종 뇌절하는 것 같다. 풀이 잘 내니까 본선에선 키보드 안 잡아도 되지 않을?까?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;kyo20111&lt;/strong&gt;: 뚜렷한 단점은 모르겠다. 굳이 잡자면 빡센 수학이나 애드혹, 십덕 알고리즘이 나왔을 때 살짝 걱정될 것 같다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;본선에선 나랑 모현이 십덕문제 외에선 입만 나불대고, 주때가 구현을 전부 담당했으면 좋겠단 욕심이 있는데 어떻게 될진 모르겠다. 양심이 살짝 찔리긴 한데 팀에 구현 잘하는 사람이 주때 말고 없다. 난 문제를 뚫어야 될 때나 주때가 잘 모르는 알고리즘 / 자료구조 써야 할 때정도만 키보드 잡는 게 이상적일 것 같다.&lt;/p&gt;
&lt;h4&gt;팀노트&lt;/h4&gt;
&lt;p&gt;뭘 짜야될진 정리돼 있는데 나 포함 아무도 안 짠다. 아무리 즐겜팟이라곤 해도 이러다 본선에 팀노트 없이 가는 건 아닐까 ㅋㅋ;&lt;/p&gt;</description>
      <author>cgiosy</author>
      <guid isPermaLink="true">https://cgiosy.tistory.com/16</guid>
      <comments>https://cgiosy.tistory.com/entry/UCPC-2022-%EC%98%88%EC%84%A0-%ED%9B%84%EA%B8%B0#entry16comment</comments>
      <pubDate>Mon, 11 Jul 2022 16:53:35 +0900</pubDate>
    </item>
    <item>
      <title>Semi-Game Cup 3 후기</title>
      <link>https://cgiosy.tistory.com/entry/Semi-Game-Cup-3-%ED%9B%84%EA%B8%B0</link>
      <description>&lt;p&gt;지인이 연 백준 대회인데, 홍보를 열심히 하고 다니길래 궁금해서 A만 풀고 스코어보드 구경이나 하려고 했다.&lt;/p&gt;
&lt;p&gt;정신을 차리니 ABCFH를 풀고 D를 뇌절하며 눈물을 흘리고 있었다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;A. 루나의 게임 세팅&lt;/h3&gt;
&lt;p&gt;$N$과 $K$가 주어질 때, $1$부터 $N$까지의 수에서 $K$개를 골라 만들 수 있는 바이토닉 순열의 개수를 출력하라.&lt;/p&gt;
&lt;p&gt;$N$개에서 $K$개 고르는 방법 수는 그냥 조합이고, 고른 다음에 바이토닉하게 $K$개를 재정렬하는 방법의 수가 핵심이었다.&lt;br&gt;생각해보면 그냥 제일 큰 거 고정하고 양쪽에 큰거부터 추가하는 것과 동치였다. $K - 1$개 왼쪽 / 오른쪽 선택하는 거니까 결국 $2^{K-1}{N \choose K}$이 답이다.&lt;/p&gt;
&lt;p&gt;풀이가 쉬워서 대회 끝나고 한 &amp;#39;A는 실5~4쯤 되지 않나?&amp;#39;라고 말하려 했는데, 생각해보니 이항계수는 실버 상위고 모듈러 역원은 골드 중상위였다. 사실 $N \le 2000$ 이라 모듈러 역원은 필요없긴 했는데, 검수 중엔 모듈러 역원 쓰는 버전을 다들 실3으로 평가했다고 한다. 내 생각에도 제곱보다 역원이 쉬운 것 같긴 하다 ㅎㅎ;&lt;/p&gt;
&lt;h3&gt;B. 다오와 트리플 멕스 게임&lt;/h3&gt;
&lt;p&gt;배열 $A$가 주어진다.&lt;br&gt;$A$에서 임의의 subsequence를 골라 $\mathbf{mex}$ 값을 배열 $B$의 끝에 추가할 수 있다.&lt;br&gt;배열 $B$에서 임의의 subarray를 골라 $\mathbf{mex}$ 값을 배열 $C$의 끝에 추가할 수 있다.&lt;br&gt;두 연산은 모두 원하는 만큼 시행 가능하다. 배열 $C$의 $\mathbf{mex}$ 값을 최대화하여라.&lt;/p&gt;
&lt;p&gt;일단 $B$에는 $0$부터 $A$의 $\mathbf{mex}$ 값까지 원하는 순서대로 원하는 만큼 추가 가능하다. 대충 $0$부터 쭉 나열해놓고 $C$로 넘어가면, 얘도 그냥 $B$의 $\mathbf{mex}$값까지 추가 가능하다. 그래서 그냥 $A$의 $\mathbf{mex}$값에 2 더한 게 답이다.&lt;br&gt;사실 예외가 있는데, $A$에 $0$이 없으면 $0$이 답이고, 그 외에 $A$의 크기가 $1$이면 $1$이 답이다.&lt;/p&gt;
&lt;p&gt;문제 설명이 살짝 꼬여 있어서 뇌절을 좀 했는데, 풀이 자체는 상당히 쉬운 편이다.&lt;/p&gt;
&lt;h3&gt;C. 공 꺼내기 게임&lt;/h3&gt;
&lt;p&gt;크기가 $N$인 배열 $A$, $B$와 확률 $q$가 주어진다. $i$가 적힌 공이 빨간 주머니에 $A_i$개, 파란 주머니에 $B_i$개 들어 있다.&lt;br&gt;빨간 주머니 혹은 파란 주머니에서 임의로 공이 주어진다. 당신은 적힌 번호 $i$를 보고, $P_i$ 확률로 빨간색이라 답하고 $1 - P_i$ 확률로 파란색이라 답해야 한다.&lt;br&gt;파란 주머니에서 꺼낸 공을 빨간 색이라 답할 확률이 $q$ 이하여야 할 때, 빨간 주머니에서 꺼낸 공을 빨간 색이라 답할 확률을 최대화하는 $P$ 배열을 구하여라.&lt;/p&gt;
&lt;p&gt;빨간 주머니에 든 공의 개수가 모두 $X$개, 파란 주머니는 $Y$개라 하자.&lt;br&gt;$i$번 공이 주어졌을 때 빨간 걸 옳게 답할 확률 $R_i$는 $\frac{P_i A_i}{X}$다. 파란 걸 틀리게 답할 확률 $Q_i$는 $\frac{P_iB_i}{Y}$다.&lt;br&gt;식을 $P_i$에 대해 정리해보면, 각각 $P_i = \frac{R_i X}{A_i}$, $P_i = \frac{Q_i Y}{B_i}$가 된다. 따라서 $R_i = Q_i \frac{Y}{X} \frac{A_i}{B_i}$가 된다. 우린 $Q_i$를 최소한으로 증가시키며 $R_i$를 최대한 증가시키고 싶다. $\frac{Y}{X}$는 상수이니 무시하면, $\frac{A_i}{B_i}$가 클 수록 $Q_i$를 늘릴 때 $R_i$도 크게 증가한다. 그래서 저걸로 정렬하고 위 정의들에 따라 $P$를 쭉 계산해주면 된다.&lt;/p&gt;
&lt;p&gt;&amp;#39;빨간 주머니 혹은 파란 주머니에서 임의로 공이 주어진다&amp;#39; 부분이 색도 랜덤하게 고른단 건지, 색을 임의로(악의적으로) 고를 수도 있단 건지 좀 모호해서 고민을 했다. 근데 다들 쓱쓱 풀길래 그냥 믿고 풀이를 냈다.&lt;br&gt;식으로 쓰니 한 플래 하위쯤 될 것 같이 보이는데 난 대충 찍고 풀어서 골드 줬다.&lt;/p&gt;
&lt;h3&gt;H. 정령과 눈 감고 숨바꼭질 게임&lt;/h3&gt;
&lt;p&gt;여기부턴 따로 디스크립션 요약을 쓰진 않겠다. 너무 길어...&lt;/p&gt;
&lt;p&gt;애들의 좌표를 다 저장하기엔 연산의 횟수 제한이 너무 빡세다. 대신 애들 $9$명이 각각 사분면 어디에 위치해 있는지는 $4^9 = 262144$ 이하의 수로 나타낼 수 있다. $23^4 = 279841$다. 그래서 그냥 2x2 격자로 쪼개고 주변 값을 쭉 읽으면 끝이다.&lt;/p&gt;
&lt;p&gt;문제를 제대로 못 봐서, &amp;#39;뭐지? 애들 무게중심을 찾아서 뭔가 해야 하나? 사실 배열 각각에 쓸 수 있는 건 페이크고 Find 연산으로 찾은 좌표 + Get으로 4개 읽어서 $\lg(200^2 \times 23^4)$ 비트 안에서 뭔가 해야 되나?&amp;#39; 하고 뻘생각을 많이 했다.&lt;br&gt;$|x| \leq 1, |y| \leq 1$란 조건을 본 순간 탄식이 나오며 $\lvert i_{k} - i_{0} \rvert &amp;gt; 1, \lvert j_{k} - j_{0} \rvert &amp;gt; 1$이랑 Get을 최대 네 번만 쓸 수 있단 조건이 달려있던 이유와 함께 1분만에 풀이가 도출됐다. 너무 허망했음...&lt;br&gt;난이도는 골1로 줬는데 동의하지 않는 사람이 꽤 많은 것 같다. 근데 플래 중상위 ~ 다이아로 평가하기엔 대회 때도 초반 솔브 빠르게 나왔던 데다가 ABC 다음으로 가장 많이 풀린 문제여서 좀 에바인 것 같다.&lt;/p&gt;
&lt;h4&gt;F. 환승역 찾기 게임&lt;/h4&gt;
&lt;p&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/20297&quot;&gt;Confuzzle&lt;/a&gt;이랑 상당히 유사한 문제다. 풀이가 너무 뻔해서, 이게 맞나? 이게 맞나?? 하고 더 좋은 풀이를 오래 고민하다가 안 떠올라서 그냥 무지성으로 복붙하고 맞았다.&lt;/p&gt;
&lt;p&gt;살짝 &lt;a href=&quot;https://www.acmicpc.net/problem/16748&quot;&gt;Colorful Tree&lt;/a&gt; 느낌도 나는데, 저게 쓸 수 있는 웰논 풀이가 훨씬 제한되기도 하고 구현도 어려운 것 같다. 그래서 저기서 두 티어 낮춘 플2로 매겼다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;D는... 대회 중엔 못 푼 게 아쉬웠는데, 끝나고 보니 너무 어렵다. 한참 틀리다가 문제를 잘못 읽은 걸 발견했는데, 발견한 뒤에도 무한 맞왜틀의 늪에서 빠져나오질 못하겠다. 안 잡길 잘한 듯&lt;/p&gt;
&lt;p&gt;대체로 상당히 재밌는 문제들이었다. D만 빼고. F도 너무 웰논이라 살짝 애매한 것 같다. 아무튼 문제 퀄리티는 운영진들의 수준에 걸맞게 꽤 높아서 잘 즐긴 것 같다.&lt;/p&gt;
&lt;p&gt;문제를 너무 자주 잘못 읽는 것 같아 슬프다. 구현도 뇌절하는 경우가 많은 느낌이다. 그냥 구현 잘하는 팀원만 믿고 목 아래론 갖다버린 다음 누가 문제 말해주면 풀이만 뱉고 싶다.&lt;/p&gt;</description>
      <author>cgiosy</author>
      <guid isPermaLink="true">https://cgiosy.tistory.com/15</guid>
      <comments>https://cgiosy.tistory.com/entry/Semi-Game-Cup-3-%ED%9B%84%EA%B8%B0#entry15comment</comments>
      <pubDate>Sun, 10 Jul 2022 18:55:49 +0900</pubDate>
    </item>
    <item>
      <title>하이퍼스레딩</title>
      <link>https://cgiosy.tistory.com/entry/%ED%95%98%EC%9D%B4%ED%8D%BC%EC%8A%A4%EB%A0%88%EB%94%A9</link>
      <description>&lt;p&gt;현대 CPU는 코어 당 한 사이클에 최대 3 - 4개 정도의 micro intrinsics를 처리 가능하다. 하지만 고도로 최적화된 프로그램이 아닌 한, 상당수가 기껏해야 0.5개 정도를 쓰는 것에 그친다.&lt;br&gt;여기엔 여러 이유가 있으나, 보통 메모리 레이턴시가 주 원인이다. CPU가 아무리 병렬화, OoO를 위해 노력해도 메모리를 읽어와야 해서 흐름이 끊기거나, 명령어들이 서로 아주 긴밀하게 얽혀 있는 등의 상황에선 성능을 완전히 활용할 수 없는 것이다.&lt;/p&gt;
&lt;p&gt;이를 위해 메모리 읽기를 비롯한 각종 연산 사이에 동일 코어에 배정된 다른 스레드를 실행하는 기술이 고안됐고, 이것이 하이퍼스레딩이다.&lt;br&gt;활성화할 경우 한 코어 위에서 여러 프로그램들이 돌아갈 때나, 멀티스레드 프로그램의 경우 성능이 유의미하게 향상되는 편이다. 이 때 CPU의 실제 코어를 물리(physical) 코어라 부르고, 각 코어에서 하이퍼스레딩이 적용되는 스레드 수를 고려한 값을 논리(logical) 코어 혹은 그냥 CPU의 스레드 수라고 한다.&lt;/p&gt;
&lt;p&gt;따라서 멀티스레드 프로그램 개발 혹은 사용 시, 해당 프로그램의 성향에 따라 실행할 스레드 수를 물리 코어 기준으로 잡을지, 논리 코어 기준으로 잡을지 고려해보는 게 좋을 듯 하다.&lt;/p&gt;
&lt;p&gt;만약 고도로 최적화된 프로그램의 경우, 메모리 레이턴시의 영향과 연산들 간의 간섭이 최소한이 되도록 설계됐을 가능성이 높다. 예로는 빠른 해시 / 압축 알고리즘 등이 있는데, 이 경우 하이퍼스레딩은 도움되지 않을 수 있다.&lt;br&gt;물론 하이퍼스레딩이 도움될 상황이 정말로 전혀 없는지는 면밀히 검토해봐야 할 것이다. 가령 압축의 경우, 압축 레벨을 높이고 더 큰 사전 파일을 사용할 수록 메모리(및 캐시) 레이턴시의 영향이 커져 하이퍼스레딩이 유의미해진다.&lt;/p&gt;
&lt;h3&gt;References&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/facebook/zstd/issues/2071#issuecomment-610002362&quot;&gt;zstd GitHub - Issue #2071&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://www.intel.com/content/www/us/en/gaming/resources/hyper-threading.html&quot;&gt;Intel - What is Hyper-Threading?&lt;/a&gt;&lt;/p&gt;</description>
      <author>cgiosy</author>
      <guid isPermaLink="true">https://cgiosy.tistory.com/14</guid>
      <comments>https://cgiosy.tistory.com/entry/%ED%95%98%EC%9D%B4%ED%8D%BC%EC%8A%A4%EB%A0%88%EB%94%A9#entry14comment</comments>
      <pubDate>Fri, 8 Jul 2022 21:45:55 +0900</pubDate>
    </item>
    <item>
      <title>Mo's Algorithm</title>
      <link>https://cgiosy.tistory.com/entry/Mos-Algorithm</link>
      <description>&lt;p&gt;$k$차원 평면에서 점 $N$개, 초직사각형 $Q$개가 주어진다. 단, 각 초직사각형에 대해 원소가 $\infty$ 혹은 $-\infty$로만 이뤄진 점이 하나 존재해야 한다.&lt;br&gt;주어진 점 집합에 단일 점을 추가하는 어떤 연산 $*$에 대해, 각 초직사각형 내부의 점들을 해당 연산으로 합친 결과를 구해야 한다. 여기서 모스(Mo&amp;#39;s) 알고리즘을 사용하면, 특정 연산들을 사용 가능할 때 $O(NQ^{1 - \frac{1}{k}})$ 정도에 문제를 해결할 수 있다.&lt;/p&gt;
&lt;h3&gt;2차원&lt;/h3&gt;
&lt;p&gt;모스 알고리즘이 가장 자주 사용되는 상황으로 예를 들어보자. 크기 $N$의 배열 $A$와 $[l, r]$ 구간 쿼리 $Q$개가 주어진다. 그러면 2차원 평면에서 $A$의 $i$번째 원소는 좌표 $(i, i)$에 위치하며 값 $A_i$를 가진 점으로, $t$번째 쿼리는 한 쪽 점이 $(\infty, -\infty)$이고 다른 쪽 점이 $(l_t, r_t)$인 직사각형으로 해석할 수 있다. 이 때 모스 알고리즘은 다음과 같은 로직으로 진행된다.&lt;/p&gt;
&lt;ol start=&quot;0&quot;&gt;
&lt;li&gt;배열을 크기 $B$의 구간으로 쪼개고 각 구간을 버킷이라 부르자. $[l, r]$ 구간 쿼리들에 대해, $l$이 속한 버킷의 번호를 $b$라 하겠다.&lt;/li&gt;
&lt;li&gt;$b$가 동일한 쿼리들은 $r$을 기준으로 정렬한다. 이러면 각 버킷에 대해 $r$은 감소하지 않으므로, 제거 없이 추가만 하면 된다.&lt;/li&gt;
&lt;li&gt;$l$은 동일 버킷에 속할 뿐 감소할 수 있으므로 $b$번 버킷에 속한 값들은 별도로 다뤄야 한다. 버킷의 크기는 $B$ 이하이니 $Q$개의 쿼리마다 나이브하게 $B$개를 추가하여 답을 구하고, 추가 전으로 복구하면 된다.&lt;/li&gt;
&lt;li&gt;이를 각 버킷에 대해 상태를 초기화해준 뒤 처리한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 때 복구 $Q$번, 초기화 $\min(Q, \frac{N}{B})$번, 추가 $QB$번이 일어난다. 만약 복구 연산과 초기화의 시간복잡도가 $O(B)$, 추가가 $O(1)$이라면 $B$의 최적값은 $\frac{N}{\sqrt{Q}}$이고, 최종 시간복잡도는 $O(N\sqrt{Q})$가 된다.&lt;/p&gt;
&lt;h4&gt;코드&lt;/h4&gt;
&lt;p&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/13548&quot;&gt;BOJ 13548. 수열과 쿼리 6&lt;/a&gt;을 버킷을 명시적으로 사용해 푼 코드이다. 버킷 크기는 256이다.&lt;/p&gt;
&lt;p&gt;위에서 $B$의 최적값을 $\frac{N}{\sqrt{Q}}$라고 했지만 사실 정확히 저 값을 잡는 경우는 실제론 많지 않다. 우선 대놓고 모스를 쓰는 문제는 보통 $N = Q$기도 하고, 연산 간 상수 차이 때문에 저 근처의 적당한 값을 잡는 경우가 많다. 문제를 풀 땐 보통 구현의 편의성을 위해 $2^k$를 버킷 크기로 잡는 편이다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;lt;bits/stdc++.h&amp;gt;
using namespace std;

struct query {
    int l, r, k;
    bool operator&amp;lt;(query const R) const {
        int a=l|255, b=R.l|255;
        return a&amp;lt;b || a==b &amp;amp;&amp;amp; r&amp;lt;R.r;
    }
};
int main() {
    ios::sync_with_stdio(0);cin.tie(0);
    int N, Q;
    cin&amp;gt;&amp;gt;N;
    int A[N];
    for(int&amp;amp;x:A) cin&amp;gt;&amp;gt;x;
    cin&amp;gt;&amp;gt;Q;
    query B[Q];
    for(int i=0; i&amp;lt;Q; i++) {
        auto&amp;amp;[l,r,k]=B[i];
        cin&amp;gt;&amp;gt;l&amp;gt;&amp;gt;r, --l, --r, k=i;
    }
    sort(B, B+Q);

    int C[100001], Res[Q];
    int s=0, e, v=0;
    auto add=[&amp;amp;](int i) {
        v=max(v, ++C[A[i]]);
    };
    auto del=[&amp;amp;](int i) {
        --C[A[i]];
    };
    for(auto[l,r,k]:B) {
        if(int t=l|255; s!=t) {
            s=e=t, v=0;
            memset(C, 0, sizeof(C));
        }
        while(e&amp;lt;r) add(++e);
        int t=min(s, r), pv=v;
        for(int i=t; i&amp;gt;=l; i--) add(i);
        for(int i=l; i&amp;lt;=t; i++) del(i);
        Res[k]=v;
        v=pv;
    }
    for(auto x:Res) cout&amp;lt;&amp;lt;x&amp;lt;&amp;lt;&amp;#39;\n&amp;#39;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;코드 (암시적 버킷)&lt;/h4&gt;
&lt;p&gt;사실 복구와 초기화를 더 강한 연산인 &amp;#39;제거 연산&amp;#39; $QB$번으로 퉁치면 기존에 잘 알려진 방법이 된다. 이는 버킷을 암시적으로 다룰 수 있는 대신 &lt;a href=&quot;https://www.acmicpc.net/problem/23238&quot;&gt;2021 ICPC A. Best Student&lt;/a&gt;처럼 제거 연산(역연산)이 존재하지 않는 문제를 풀기 어려워진다.&lt;/p&gt;
&lt;p&gt;다만 위에서 예로 든 수열과 쿼리 6의 경우, 제거 연산이 아주 쉽게 지원되기 때문에 어떤 방식으로든 풀 수 있다. 아래 코드가 예시이며, 속도는 그리 차이나지 않는다. 버킷 크기는 512이다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;lt;bits/stdc++.h&amp;gt;
using namespace std;

struct query {
    int l, r, k;
    bool operator&amp;lt;(query const R) const {
        int a=l&amp;gt;&amp;gt;9, b=R.l&amp;gt;&amp;gt;9;
        return a&amp;lt;b || a==b &amp;amp;&amp;amp; (a&amp;amp;1 ? r&amp;gt;R.r : r&amp;lt;R.r);
    }
};
int main() {
    ios::sync_with_stdio(0);cin.tie(0);
    int N, Q;
    cin&amp;gt;&amp;gt;N;
    int A[N];
    for(int&amp;amp;x:A) cin&amp;gt;&amp;gt;x;
    cin&amp;gt;&amp;gt;Q;
    query B[Q];
    for(int i=0; i&amp;lt;Q; i++) {
        auto&amp;amp;[l,r,k]=B[i];
        cin&amp;gt;&amp;gt;l&amp;gt;&amp;gt;r, --l, --r, k=i;
    }
    sort(B, B+Q);

    int C[100001]{}, D[N+1]{}, Res[Q];
    int s=0, e=-1, v=0;
    auto add=[&amp;amp;](int i) {
        v+=!D[++C[A[i]]]++;
    };
    auto del=[&amp;amp;](int i) {
        v-=!--D[C[A[i]]--];
    };
    for(auto[l,r,k]:B) {
        while(l&amp;lt;s) add(--s);
        while(e&amp;lt;r) add(++e);
        while(s&amp;lt;l) del(s++);
        while(r&amp;lt;e) del(e--);
        Res[k]=v;
    }
    for(auto x:Res) cout&amp;lt;&amp;lt;x&amp;lt;&amp;lt;&amp;#39;\n&amp;#39;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;정렬 방식이 살짝 다른 것을 볼 수 있는데, 아래에서 설명한다.&lt;/p&gt;
&lt;h4&gt;팁&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;$\infty$와 $-\infty$로만 이뤄진 점에 대해서, 두 값은 방향을 의미한다. 가령 위 예시에서 $(\infty, -\infty)$와 $(l, r)$의 의미는 $l \le x_i \wedge y_i \le r$인 점들을 직사각형에 포함하겠단 의미이다.&lt;ul&gt;
&lt;li&gt;만약 두 번째 원소를 $\infty$로 바꿔서, $(\infty, \infty)$와 $(l, r)$의 경우 $l \le x_i \wedge r \le y_i$인 점들을 포함한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;버킷 크기는 많은 상황에서 512나 256정도가 가장 빠른 것 같다.&lt;/li&gt;
&lt;li&gt;암시적 버킷 형태로 풀 때 버킷의 번호가 짝수면 $r$이 단조증가, 홀수면 단조감소하도록 정렬하면 좀 더 빠르다. 다른 버킷으로 넘어갈 때마다 $r$을 싹 감소시켜버리는 대신, 직전 버킷에서의 결과를 사용하기 때문이다.&lt;/li&gt;
&lt;li&gt;복구는 이전과 달라진 부분들을 다시 이전 값으로 되돌리는 것으로 생각할 수 있다. 보통 바뀔 예정인 값들(특히 쿼리의 답)을 미리 저장해놓고, 복구는 해당 값들로 다시 바꾸면 된다.&lt;/li&gt;
&lt;li&gt;모든 값을 저장하고 복구하긴 힘들 수 있다. 이것들만 제거 연산으로 빼는 것을 고려하자. 다행히 복구 연산이 없을 때에 비해 관리해야 할 상태가 적으므로 한결 수월하다. 제거할 때 영향이 미치는 값들 중 일부는 복구로 인해 제거와 독립적으로 처리되기 때문이다.&lt;/li&gt;
&lt;li&gt;혹은 아예 버킷에 속한 값을 부저장소에서 따로 관리할 수도 있겠다. 하지만 모스를 사용하게 되는 이유 자체가 평방분할조차 불가능한, 즉 저장소의 분리가 매우 까다로운 경우이기에 저게 가능한 상황은 거의 보지 못한 것 같다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3차원&lt;/h3&gt;
&lt;p&gt;2차원 때와 비슷한 예를 들어보겠다. 기존과 비슷하지만 쿼리에 더해, 어떤 점을 추가하는 업데이트가 존재할 수 있다고 하자. 이 경우 초기 $A$의 점의 위치는 $(i, i, 0)$으로, $t$번째 쿼리가 구간 쿼리일 경우 $(\infty, -\infty, -\infty)$과 $(l_t, r_t, t)$로 이뤄진 직사각형으로, $t$번째 쿼리가 업데이트일 경우 $(k_t, k_t, t)$ 위치에 점을 추가하는 것으로 해석할 수 있다.&lt;/p&gt;
&lt;p&gt;(작성 중)&lt;/p&gt;</description>
      <author>cgiosy</author>
      <guid isPermaLink="true">https://cgiosy.tistory.com/13</guid>
      <comments>https://cgiosy.tistory.com/entry/Mos-Algorithm#entry13comment</comments>
      <pubDate>Thu, 7 Jul 2022 11:35:37 +0900</pubDate>
    </item>
    <item>
      <title>알고리즘 문제 풀 때 쓰는 해시 종류들</title>
      <link>https://cgiosy.tistory.com/entry/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%AC%B8%EC%A0%9C-%ED%92%80-%EB%95%8C-%EC%93%B0%EB%8A%94-%ED%95%B4%EC%8B%9C-%EC%A2%85%EB%A5%98%EB%93%A4</link>
      <description>&lt;h4&gt;순서 보존 (문자열, 순열 등)&lt;/h4&gt;
&lt;p&gt;이 경우, 라빈 카프 해싱으로 잘 알려진 $\sum_{i=1}^{n}{c^{n-i} A_i}$를 쓰는 게 일반적이다.&lt;br&gt;그냥 쓰긴 너무 크니까 보통 적당한 자연수 $m$로 나눈 나머지를 쓰는데, $c$와 $m$이 서로소이며 $A_i$의 최댓값보다 커야 품질이 좋다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$A_i$보다 작으면 서로 다른 $A$가 동일한 해시값을 가질 수 있다. ($\mod m$을 안 붙였을 때 기준) 즉 중복이 발생한다.&lt;/li&gt;
&lt;li&gt;$c$와 $m$이 서로소가 아니면 $\mod m$ 했을 때 0 나오기 쉽다. 그리고 굳이 이게 아니어도 딱히 좋진 않을 듯&lt;/li&gt;
&lt;li&gt;$c$를 $m$의 원시근으로 잡으면 좋긴 한데, 사실 PS 범위에선 그까진 필요없고 $c^0, ..., c^{1,000,000}$에서 중복이 없는 정도면 충분하다. 근데 $m = 10^9+7, 2^{32}, 2^{64}$ 등에서 홀수 상당수가 저거 만족하니까 그냥 신경 안 써도 될 것 같다.&lt;/li&gt;
&lt;li&gt;$c^{n-i}$ 대신 $c^i$를 쓰면 롤링 해시로 쓸 때 나눗셈이 필요해져서 모듈로 역원을 써야 할 수 있다. 되도록이면 기존 형태대로 쓰자.&lt;/li&gt;
&lt;li&gt;계산 시 편의상 $m$을 고정된 상수($10^9+7$이라든지)로 잡으면 &lt;a href=&quot;http://www.secmem.org/blog/2019/12/15/RabinKarpCollision/&quot;&gt;저격&lt;/a&gt;을 당할 수도 있다. 이 때 $A_i$ 그대로 쓰는 대신, $A$의 어떤 원소 $x$에 대한 랜덤한 값 $R_x$를 런타임에 만들고 $R_{A_i}$를 쓸 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;순서 보존 X (중복집합 등)&lt;/h4&gt;
&lt;p&gt;정렬이 가능하다면, 즉 중복집합의 원소가 모두 주어져 있고 이들 간에 비교가 가능하다면, 정렬하고 적당한 해시 쓰는 게 제일 쉽다.&lt;/p&gt;
&lt;p&gt;정렬이 불가능하다면, 예를 들어 비교가 불가능하거나 원소가 실시간으로 추가된다면, 집합 $S$의 각 원소 $x$에 대해 랜덤 값 $R_x$를 적절히 만들며 $\sum_{x \in S}{R_x}$를 쓰는 게 나을 수 있다.&lt;/p&gt;
&lt;p&gt;사실 PS 범위에서 후자의 케이스에 쓸만한 직관적인 방법 중 가장 좋은 건, 랜덤한 소수 $P_x$들을 만든 후 각 원소 $x$에 대해 배정해주며 $\prod_{x \in S}{P_x}$를 구하는 것이다. 각 중복집합에 대응되는 합성수가 유일함은 자명하다 (혹시 잘 모르겠으면 소인수분해를 생각하자).&lt;br&gt;다만 소수 구하는 코드를 짜넣음으로써 희생하는 속도나 구현량 등을 감안할 만큼 메리트가 있는 경우는 적은 것 같다. 위의 단순 합만으로도 해시가 겹치기 쉽진 않아서...&lt;br&gt;만약 이를 사용한다면 주의할 점으로, $\mod m$을 씌울 때 $m$이 소수면 상관없지만 $2^k$ 형태일 경우 $P_x$에 $2$를 쓰면 망한다. $3$ 이상의 소수만 구하도록 하자.&lt;/p&gt;
&lt;h4&gt;트리&lt;/h4&gt;
&lt;p&gt;루트가 있다면, 서브트리의 해시로 간선을 정렬하고 순서를 보존하며 해시 목록을 해싱하면 된다.&lt;/p&gt;
&lt;p&gt;루트가 없다면, 각 정점이 루트일 때의 해시를 구한 뒤, 순서를 무시하고 해싱해야 한다.&lt;br&gt;각 정점을 루트로 봐야 할 때 몇몇 조건을 만족하는 트리DP를 부분합 등으로 $O(N)$에 하는 테크닉은 잘 알려져 있고, 어렵지 않게 답을 구할 수 있다. 다만 DP를 할 때 부모 간선 위치를 잘 고려해주지 않으면 순서가 꼬일 수 있으니 주의하자.&lt;/p&gt;</description>
      <author>cgiosy</author>
      <guid isPermaLink="true">https://cgiosy.tistory.com/12</guid>
      <comments>https://cgiosy.tistory.com/entry/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%AC%B8%EC%A0%9C-%ED%92%80-%EB%95%8C-%EC%93%B0%EB%8A%94-%ED%95%B4%EC%8B%9C-%EC%A2%85%EB%A5%98%EB%93%A4#entry12comment</comments>
      <pubDate>Thu, 7 Jul 2022 10:55:37 +0900</pubDate>
    </item>
    <item>
      <title>HTTP 프록시 서버 벤치마크</title>
      <link>https://cgiosy.tistory.com/entry/HTTP-%ED%94%84%EB%A1%9D%EC%8B%9C-%EC%84%9C%EB%B2%84-%EB%B2%A4%EC%B9%98%EB%A7%88%ED%81%AC</link>
      <description>&lt;h1&gt;HTTP Proxy Server Benchmarks&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;h2load&lt;/code&gt;를 사용해 리버스 프록시 서버로서의 &lt;a href=&quot;https://github.com/h2o/h2o&quot;&gt;H2O&lt;/a&gt;와 &lt;a href=&quot;https://www.nginx.com/&quot;&gt;Nginx&lt;/a&gt;를 벤치마크 및 비교해 보았다. HTTP/2와 HTTP/3 각각에 대해 테스트하였고, 벤치마크 명령어를 다섯 번 실행하여 시간이 가장 적게 소모된 경우를 뽑았다.&lt;/p&gt;
&lt;h2&gt;Configurations&lt;/h2&gt;
&lt;h3&gt;H2O&lt;/h3&gt;
&lt;p&gt;2022년 1월 8일 기준 &lt;a href=&quot;https://github.com/h2o/h2o/commit/417923e4c031cd2b69ce58652c6b9f348a336392&quot;&gt;최신 커밋&lt;/a&gt;을 빌드하였으며, &lt;a href=&quot;https://github.com/h2o/h2o/pull/1793&quot;&gt;ECDH Curves&lt;/a&gt; 관련 PR을 적용했다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;h2o.conf&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;send-server-name: OFF
access-log: /app/log/h2o-access.log
error-log: /app/log/h2o-error.log
error-log.emit-request-errors: ON

hosts:
  &amp;quot;localhost&amp;quot;:
    listen: &amp;amp;www_ssl_listen
      port: 443
      ssl:
        certificate-file: /certs/localhost.pem
        key-file: /certs/localhost.key
        min-version: TLSv1.2
        ecdh-curves: X25519:P-521:P-384
        cipher-suite: ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256
        cipher-suite-tls1.3: [TLS_AES_256_GCM_SHA384, TLS_AES_128_GCM_SHA256, TLS_CHACHA20_POLY1305_SHA256]
    listen:
      type: quic
      &amp;lt;&amp;lt;: *www_ssl_listen
    compress:
      br: 6
    file.send-compressed: ON
    paths:
      &amp;quot;/&amp;quot;:
        proxy.reverse.url: &amp;quot;http://localhost:3000/&amp;quot;
        proxy.timeout.first_byte: 600000&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Nginx&lt;/h3&gt;
&lt;p&gt;2022년 1월 8일 기준 3개월 전에 갱신된 &lt;a href=&quot;https://hub.docker.com/r/ranadeeppolavarapu/nginx-http3&quot;&gt;ranadeeppolavarapu/nginx-http3&lt;/a&gt;를 사용하였다. 버전은 1.19.5라는 것 같다.&lt;/p&gt;
&lt;p&gt;아래 설정 파일에서 &lt;code&gt;map&lt;/code&gt;이나 &lt;code&gt;add_header&lt;/code&gt; 등의 구문들을 빼고 실행해 보았었으나, 실행 시간에 유의미한 향상은 없었다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;nginx.conf&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;user                 nginx;
pid                  /var/run/nginx.pid;
worker_processes     auto;
worker_rlimit_nofile 65535;

events {
    multi_accept       on;
    worker_connections 65535;
}

http {
    upstream proxy {
        server 0.0.0.0:3000;
    }

    charset                utf-8;
    sendfile               on;
    tcp_nopush             on;
    tcp_nodelay            on;
    server_tokens          off;
    log_not_found          off;
    types_hash_max_size    2048;
    types_hash_bucket_size 64;
    client_max_body_size   16M;

    # MIME
    include                mime.types;
    default_type           application/octet-stream;

    # Logging
    access_log             /var/log/nginx/access.log;
    error_log              /var/log/nginx/error.log warn;

    # SSL
    ssl_session_timeout    1d;
    ssl_session_cache      shared:SSL:10m;
    ssl_session_tickets    off;

    # Mozilla Modern configuration
    ssl_protocols          TLSv1.3;

    # OCSP Stapling
    ssl_stapling           on;
    ssl_stapling_verify    on;
    resolver               1.1.1.1 1.0.0.1 valid=60s;
    resolver_timeout       2s;

    # Connection header for WebSocket reverse proxy
    map $http_upgrade $connection_upgrade {
        default upgrade;
        &amp;quot;&amp;quot;      close;
    }

    map $remote_addr $proxy_forwarded_elem {

        # IPv4 addresses can be sent as-is
        ~^[0-9.]+$        &amp;quot;for=$remote_addr&amp;quot;;

        # IPv6 addresses need to be bracketed and quoted
        ~^[0-9A-Fa-f:.]+$ &amp;quot;for=\&amp;quot;[$remote_addr]\&amp;quot;&amp;quot;;

        # Unix domain socket names cannot be represented in RFC 7239 syntax
        default           &amp;quot;for=unknown&amp;quot;;
    }

    server {
        listen 443 quic reuseport;
        listen 443 ssl http2;
        server_name localhost;

        http2_push_preload on;

        brotli_static on;
        brotli on;
        brotli_types text/plain text/css application/json application/javascript application/x-javascript text/javascript;
        brotli_comp_level 6;

        ssl_protocols TLSv1.2 TLSv1.3;

        ssl_certificate /etc/ssl/localhost.pem;
        ssl_certificate_key /etc/ssl/private/localhost.key;

        ssl_session_cache shared:SSL:10m;
        ssl_session_timeout 5m;
        ssl_session_tickets off;

        # Enable TLSv1.3&amp;#39;s 0-RTT. Use $ssl_early_data when reverse proxying to
        # prevent replay attacks.
        #
        # @see: http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_early_data
        ssl_early_data on;
        ssl_ciphers HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers on;

        add_header alt-svc &amp;#39;h3-29=&amp;quot;:443&amp;quot;; ma=86400, h3=&amp;quot;:443&amp;quot;; ma=86400&amp;#39;;
        # Debug 0-RTT.
        add_header X-Early-Data $tls1_3_early_data;

        add_header x-frame-options &amp;quot;deny&amp;quot;;
        add_header Strict-Transport-Security &amp;quot;max-age=31536000&amp;quot; always;

        location / {
          proxy_pass http://proxy;
        }
    }

    map $ssl_early_data $tls1_3_early_data {
        &amp;quot;~.&amp;quot; $ssl_early_data;
        default &amp;quot;&amp;quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Ubuntu&lt;/h3&gt;
&lt;p&gt;잘 알려진 몇 가지 간단한 트윅을 추가했다. &lt;a href=&quot;https://blog.cloudflare.com/http-2-prioritization-with-nginx/&quot;&gt;참고&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/etc/sysctl.conf&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
net.ipv4.tcp_notsent_lowat = 16384&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Web Server&lt;/h3&gt;
&lt;p&gt;Go 언어의 &lt;a href=&quot;https://github.com/gofiber/fiber&quot;&gt;Fiber 라이브러리&lt;/a&gt;를 사용해 1000B 길이의 단순 문자열을 출력하는 프로그램을 썼다. 문자열은 적당한 랜덤 문자열을 하드코딩했는데, 벤치마크에 더 적절한 방법이 있는지 궁금하다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;package main

import &amp;quot;log&amp;quot;
import &amp;quot;github.com/gofiber/fiber/v2&amp;quot;

func main() {
    app := fiber.New()

    app.Get(&amp;quot;/test&amp;quot;, func(c *fiber.Ctx) error {
        return c.SendString(&amp;quot;QHo&amp;gt;&amp;#39;r3ro;nX8[-)V`=Pz5._go4K\\MW! -Q;sc%F9,=\&amp;quot;oTc)r+6;]e#{e6CcViDy#5oS+L@Vbxha53+Wa.t=#hg&amp;gt;$!+hh!E0kg&amp;#39;Qpl@\&amp;quot;iDT{v\\)D]W!|OFv)}$$F/xvQf2#g_9BGlazCzP3W+?/7BNwGj{HG&amp;lt;Rk(?n../NSJF.xlA&amp;gt;TvGCZj7WkDjC,Q;w5`VTG&amp;gt;?Ntq})\\V0Os3m*l@vEy%K|#H5fs+Q5+-jf?*h|T@Bae;ubH&amp;#39;5bHwh`Cqh+h7\&amp;quot;DdkBIqU88AXtc/7SG%a&amp;lt;4`hwOOJI=Vzx(]0ESc0%1Y+6 ax &amp;amp;&amp;#39;PDj(Rfz\\,7!HuA(y&amp;#39;dFpm`bz6]n$^f\\OC^p\\,D}{Veh3IV(ZW`+n%`yAk8U_aElPwv5BI+NSFxJ&amp;gt;=C}go}[!f#{+6rbOrL^8:;NsBZu&amp;lt;zy9)de&amp;lt;JL%%\\Q|R,TaQDfo7K)q\&amp;quot;*&amp;#39;bn&amp;gt;iPLcwiXh7J[iim&amp;lt;HoyG[gkxJ:{|btsL&amp;lt;3knwz&amp;lt;aiE5N4J26\&amp;quot;PLS:C-)KUx&amp;gt;VzrBH(M8myJvKX{B#H]smsimNx`#)8X \&amp;quot;A 9PzbRxqDh1=+&amp;gt;24Yvk=v5q&amp;amp;d{s6D}L,I ^+hIDmow?P IQ^u]0C)c6goZe1FMbHtsWc_{\&amp;quot;tVt6&amp;lt;kn|x%(,#X=g0MRzo6MByN#0jS&amp;gt;U$^pX+4yU[c.WuC%sw90ppaWHP_&amp;amp;:_ZG!ZviYml7&amp;#39;02},3x$5iIT[%4M3$M+VG+fy0j}*{a+K_-@ P9;:&amp;lt;X`+zMlQ(3\\lX d}puIqVoBfATmi482e//k0n{z4]n``Bl/Sd9l{_T8ONy^9&amp;gt;e#&amp;lt;&amp;amp;`{N5vJO-[Rvn vYe`r%4WAcEtwKG5;r9&amp;gt;sDXZLI]YT6y_Ke2xz+\\@_2X&amp;gt;M%04 @en^s8^0&amp;amp;Rwc7TD_-&amp;gt;d091j_cS:3hEX6)6SS;&amp;amp;apmo(nn+zRw0TA*&amp;gt;1gFr)u@sfNV=BR\\xF0BF2}9c6VO&amp;amp;M:%&amp;lt;fa\\#0u-ds#yq=Hp6zS&amp;amp;W3|\&amp;quot;T,IXuP2&amp;lt;lJt/Lp&amp;amp;%@mc@p\&amp;quot;3&amp;amp;Gd!c:Y)XB|rmKMBR+uW&amp;#39;E&amp;quot;)
    })

    log.Fatal(app.Listen(&amp;quot;:3000&amp;quot;))
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;HTTP/2 (100 connections)&lt;/h2&gt;
&lt;p&gt;Command: &lt;code&gt;./h2load -t 2 -m 10 -c 100 -n 100000 https://localhost/test&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;H2O&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;finished in 1.88s, 53256.45 req/s, 52.22MB/s
requests: 100000 total, 100000 started, 100000 done, 100000 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 100000 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 98.05MB (102812500) total, 985.16KB (1008800) headers (space savings 89.71%), 95.37MB (100000000) data
                     min         max         mean         sd        +/- sd
time for request:      218us    182.66ms     15.84ms      9.54ms    89.99%
time for connect:     5.01ms    165.09ms     60.79ms     37.48ms    70.00%
time to 1st byte:    21.02ms    192.11ms    136.26ms     34.47ms    69.00%
req/s           :     532.96      752.54      614.27       77.88    55.00%&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Nginx&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;finished in 7.29s, 13717.50 req/s, 15.27MB/s
requests: 100000 total, 100000 started, 100000 done, 100000 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 100000 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 111.30MB (116706600) total, 14.21MB (14900000) headers (space savings 31.96%), 95.37MB (100000000) data
                     min         max         mean         sd        +/- sd
time for request:     2.18ms    415.23ms     61.96ms     26.88ms    86.04%
time for connect:     6.38ms    388.22ms    143.27ms    101.13ms    65.00%
time to 1st byte:    55.66ms    480.30ms    341.13ms    122.16ms    83.00%
req/s           :     137.45      727.51      194.18      157.12    92.00%&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;HTTP/2 (1000 connections)&lt;/h2&gt;
&lt;p&gt;Command: &lt;code&gt;./h2load -t 2 -m 10 -c 1000 -n 100000 https://localhost/test&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;H2O&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;finished in 32.61s, 3066.87 req/s, 3.01MB/s
requests: 100000 total, 100000 started, 100000 done, 100000 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 100000 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 98.14MB (102902322) total, 1.02MB (1065322) headers (space savings 89.13%), 95.37MB (100000000) data
                     min         max         mean         sd        +/- sd
time for request:       69us      30.93s       1.57s       5.51s    92.39%
time for connect:    53.18ms       1.67s    800.99ms    461.85ms    56.70%
time to 1st byte:   282.24ms      29.50s       6.04s       8.66s    81.50%
req/s           :       3.07       59.92       13.32       17.00    85.40%&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Nginx&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;finished in 33.81s, 2957.42 req/s, 3.29MB/s
requests: 100000 total, 100000 started, 100000 done, 100000 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 100000 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 111.34MB (116749000) total, 14.21MB (14900000) headers (space savings 31.96%), 95.37MB (100000000) data
                     min         max         mean         sd        +/- sd
time for request:      200us      31.86s       1.65s       3.45s    95.34%
time for connect:    66.43ms       2.04s    908.46ms    468.91ms    62.10%
time to 1st byte:      1.65s      33.27s       7.26s       7.12s    78.40%
req/s           :       2.96       10.24        6.34        1.94    46.00%&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;HTTP/3 (100 connections)&lt;/h2&gt;
&lt;p&gt;Command: &lt;code&gt;./h2load --npn-list h3 -t 2 -m 10 -c 100 -n 100000 https://localhost/test&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;H2O&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;finished in 2.72s, 36792.61 req/s, 37.12MB/s
requests: 100000 total, 100000 started, 100000 done, 100000 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 100000 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 100.90MB (105800500) total, 5.05MB (5300000) headers (space savings 45.92%), 95.37MB (100000000) data
UDP datagram: 23465 sent, 102023 received
                     min         max         mean         sd        +/- sd
time for request:      349us    156.32ms     26.13ms      6.28ms    98.23%
time for connect:     4.79ms    144.78ms     58.79ms     32.01ms    69.00%
time to 1st byte:   103.59ms    167.34ms    134.65ms     17.36ms    66.00%
req/s           :     369.13      394.19      374.30        3.82    80.00%&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Nginx&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;finished in 6.32s, 15811.57 req/s, 16.94MB/s
requests: 100000 total, 100000 started, 100000 done, 100000 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 100000 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 107.11MB (112308809) total, 10.97MB (11500000) headers (space savings 51.68%), 95.37MB (100000000) data
UDP datagram: 37347 sent, 110482 received
                     min         max         mean         sd        +/- sd
time for request:      917us    158.01ms     57.60ms     21.09ms    65.72%
time for connect:    98.08ms    201.08ms    154.52ms     25.34ms    70.00%
time to 1st byte:   150.34ms    294.50ms    233.62ms     36.59ms    73.00%
req/s           :     158.17      192.97      169.58       11.52    80.00%&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;HTTP/3 (1000 connections)&lt;/h2&gt;
&lt;p&gt;Command: &lt;code&gt;./h2load --npn-list h3 -t 2 -m 10 -c 1000 -n 100000 https://localhost/test&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;H2O&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;finished in 4.52s, 22142.04 req/s, 22.34MB/s
requests: 100000 total, 100000 started, 100000 done, 100000 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 100000 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 100.90MB (105805000) total, 5.05MB (5300000) headers (space savings 45.92%), 95.37MB (100000000) data
UDP datagram: 25928 sent, 104305 received
                     min         max         mean         sd        +/- sd
time for request:      179us       3.10s     92.30ms    191.70ms    94.60%
time for connect:    69.01ms       3.32s       1.34s       1.13s    62.40%
time to 1st byte:   431.29ms       4.19s       1.73s       1.10s    44.20%
req/s           :      22.65      112.64       54.87       26.70    51.90%&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Nginx&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;finished in 24.36s, 4105.82 req/s, 4.40MB/s
requests: 100000 total, 100000 started, 100000 done, 100000 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 100000 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 107.18MB (112388030) total, 10.97MB (11500000) headers (space savings 51.68%), 95.37MB (100000000) data
UDP datagram: 27441 sent, 115823 received
                     min         max         mean         sd        +/- sd
time for request:      370us      17.90s       1.39s       2.77s    94.08%
time for connect:   552.44ms       3.48s       1.67s       1.06s    63.90%
time to 1st byte:   634.64ms      18.49s       8.08s       6.71s    52.60%
req/s           :       4.11       13.38        7.24        2.60    48.80%&lt;/code&gt;&lt;/pre&gt;</description>
      <author>cgiosy</author>
      <guid isPermaLink="true">https://cgiosy.tistory.com/11</guid>
      <comments>https://cgiosy.tistory.com/entry/HTTP-%ED%94%84%EB%A1%9D%EC%8B%9C-%EC%84%9C%EB%B2%84-%EB%B2%A4%EC%B9%98%EB%A7%88%ED%81%AC#entry11comment</comments>
      <pubDate>Thu, 7 Jul 2022 10:13:21 +0900</pubDate>
    </item>
    <item>
      <title>최적의 글줄 너비와 높이, 글자 당 평균 너비</title>
      <link>https://cgiosy.tistory.com/entry/%EC%B5%9C%EC%A0%81%EC%9D%98-%EA%B8%80%EC%A4%84-%EB%84%88%EB%B9%84%EC%99%80-%EB%86%92%EC%9D%B4-%EA%B8%80%EC%9E%90-%EB%8B%B9-%ED%8F%89%EA%B7%A0-%EB%84%88%EB%B9%84</link>
      <description>&lt;p&gt;인터넷을 확인해보아도 정확한 자료가 없어 직접 측정 후 정리해 보았다. 적절한 폭과 높이는 흔히 알려진 기준을 기반으로, 두 시간정도 모니터를 뚫어지게 쳐다보며 직접 값을 튜닝해본 결과를 썼다.&lt;/p&gt;
&lt;p&gt;영문(알파벳 + 특수 문자, 커닝 포함) 글의 글자 당 평균 너비:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;산세리프 (Arial, Pretendard, 산돌네오): &lt;strong&gt;0.45em&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;세리프 (Times New Roman): &lt;strong&gt;0.4em&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.daviddlewis.com/resources/testcollections/reuters21578/&quot;&gt;Reuters-21578&lt;/a&gt; 데이터를 넣고 손수 측정해본 결과이다. 굵기는 400 기준이며, 700으로 할 시 0.02em 이내의 차이가 발생했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;한국어(한글 + 영문자 포함) 글의 글자 당 평균 너비:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;산세리프 (Pretendard, 산돌네오, 본고딕): &lt;strong&gt;0.7em&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;뉴스를 위주로 해 임의의 인터넷 글들을 넣어보며 측정했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;화면에서 적절한 글줄의 폭 (영문 글 평균 너비 기준, 글자 단위):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;단문 위주 (SNS, 커뮤니티 등): &lt;strong&gt;75개&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;장문 위주 (블로그, 뉴스, 문서 등): &lt;strong&gt;85 - 95개&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;단문 위주 SNS인 트위터는 현재 75개를 폭으로 잡고 있다. 장문일 경우 &lt;a href=&quot;https://web.archive.org/web/20150619221256/http://psychology.wichita.edu/surl/usabilitynews/72/LineLength.asp&quot;&gt;85 - 95개가 읽기 속도가 가장 빠르단 연구&lt;/a&gt; 및 &lt;a href=&quot;https://www.smashingmagazine.com/2014/09/balancing-line-length-font-size-responsive-web-design&quot;&gt;의견&lt;/a&gt;이 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;화면에서 적절한 글줄의 높이 (두 규칙 중 하나를 선택해 수정하여 사용)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;헤딩에 &lt;code&gt;1em + 0.5rem&lt;/code&gt;, 본문에 &lt;code&gt;2em - 0.5rem&lt;/code&gt; 정도를 사용한다. (머티리얼 디자인의 &lt;a href=&quot;https://m3.material.io/styles/typography/type-scale-tokens&quot;&gt;Typography&lt;/a&gt; 참고)&lt;/li&gt;
&lt;li&gt;헤딩에 &lt;strong&gt;1.2em&lt;/strong&gt;, 본문에 &lt;strong&gt;1.5em&lt;/strong&gt; 를 기준으로 적당히 바꾼다. &lt;a href=&quot;https://blog.prototypr.io/space-hierarchy-dbfbaa03d97a&quot;&gt;참고&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;다만 둘 모두 소문자가 많은 영문 기준이며, 한글은 정사각형을 꽉 채우는 형태이기에 조금 더 넓게 잡는 게 좋을 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;letter-spacing&lt;/code&gt;과 &lt;code&gt;word-spacing&lt;/code&gt;도 가독성에 꽤나 영향을 미친다고 한다. 다만 폰트들 자체적으로 상당히 최적화되어 있을 거라 믿기도 하고, 일단 한국어(한글)에 대한 자료가 너무 없어서 분석을 보류한다.&lt;/p&gt;</description>
      <author>cgiosy</author>
      <guid isPermaLink="true">https://cgiosy.tistory.com/10</guid>
      <comments>https://cgiosy.tistory.com/entry/%EC%B5%9C%EC%A0%81%EC%9D%98-%EA%B8%80%EC%A4%84-%EB%84%88%EB%B9%84%EC%99%80-%EB%86%92%EC%9D%B4-%EA%B8%80%EC%9E%90-%EB%8B%B9-%ED%8F%89%EA%B7%A0-%EB%84%88%EB%B9%84#entry10comment</comments>
      <pubDate>Thu, 7 Jul 2022 10:12:25 +0900</pubDate>
    </item>
    <item>
      <title>압축 속도별 최적 알고리즘</title>
      <link>https://cgiosy.tistory.com/entry/%EC%95%95%EC%B6%95-%EC%86%8D%EB%8F%84%EB%B3%84-%EC%B5%9C%EC%A0%81-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;(아직 미완성인 글이다. 추후 더 정확히 벤치마크해서 자료를 추가할 예정.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 통신 및 스트리밍할 때, 대역폭(IO나 네트워크 속도)보다 빠른 압축 알고리즘은 보통 필요치 않다. 이 글에선 사용례에 따라 잘 알려진 압축 알고리즘들을 분류해본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오라클 Ampere A1과 맥북 에어 M1 CPU를 기준으로 측정했다. 일반적인 클라우드의 CPU 성능은 이 사이에 존재하는 편이기에, 중간 정도의 값을 테스트해보고 결과에 따라 압축 수준을 낮추거나 높이도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;싱글 스레드를 기준으로 하며, 멀티 스레드의 경우 대역폭을 CPU의 물리 코어 수로 나눠서 보면 될 것 같다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Disk I/O&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽기와 쓰기에 대한 대역폭이 제한된 것이므로, &lt;code&gt;(원본 크기 + 압축 크기) / 소요 시간 / 1 MiB&lt;/code&gt;을 기준으로 계산.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;1000 MB/s: &lt;code&gt;lz4 1&lt;/code&gt; - &lt;code&gt;zstd 1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;250 MB/s: &lt;code&gt;zstd 1 - 3&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;100 MB/s: &lt;code&gt;zstd 6&lt;/code&gt; - &lt;code&gt;brotli 5 - 6&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;네트워크&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰기에 대한 대역폭이 제한된 것이므로, &lt;code&gt;압축 크기 / 소요 시간 / 1 MiB&lt;/code&gt;을 기준으로 계산. 대역폭은 클라이언트 기준. 압축 후 크기는 원본의 1/3 정도임을 가정한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;100 MB/s: &lt;code&gt;zstd 3 - 6&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;10 MB/s: &lt;code&gt;brotli 5 - 9&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 &lt;code&gt;brotli 10 - 11&lt;/code&gt;의 압축 속도는 아주 느리다. 미리 압축해두는 게 아닌, 실시간 압축에 10 이상을 적용하는 건 다시 생각해보는 것을 권한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;아카이빙&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;압축 해제 속도가 압축 속도보다 크게 중요하다면 &lt;code&gt;brotli 11&lt;/code&gt;과 &lt;code&gt;zstd 22&lt;/code&gt;를, 그렇지 않다면 &lt;a href=&quot;https://github.com/IlyaGrebnov/libbsc&quot;&gt;bsc&lt;/a&gt;와 &lt;code&gt;lzma&lt;/code&gt;도 고려해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;압축 해제 / 압축 속도 / 압축률이 좋은 것부터 나열하면 대충 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;압축 해제 속도: &lt;code&gt;zstd 22 &amp;gt; brotli 11 &amp;gt; lzma &amp;gt; bsc&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;압축 속도: &lt;code&gt;bsc &amp;gt; lzma &amp;gt;= zstd 22 &amp;gt; brotli 11&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;압축률: &lt;code&gt;bsc &amp;gt; brotli 11 &amp;gt; zstd 22 &amp;gt; lzma&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사실 압축률은 매우 데이터 의존적이라 큰 의미는 없다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bsc&lt;/code&gt;는 다량의 글이 포함된 데이터에서 특히 강하다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;brotli&lt;/code&gt;는 소스 코드를 비롯한 다양한 파일에서 강하다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lzma&lt;/code&gt;는 대체로 썩 좋지 않으나 가끔씩 가장 나은 압축률을 보여준다. 굳이 꼽자면 바이너리 계열에서 강세를 보이는 편이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 평범한 파일에는 기능이 많고 무난한 성능을 보여주는 &lt;code&gt;zstd 16 - 22&lt;/code&gt; 정도를 선호할 것 같다. 참고로 zstd는 멀티 스레딩, &lt;a href=&quot;https://github.com/facebook/zstd/wiki/Zstandard-as-a-patching-engine&quot;&gt;diff 추출&lt;/a&gt;, &lt;a href=&quot;https://github.com/facebook/zstd#the-case-for-small-data-compression&quot;&gt;간편한 사전 파일 생성&lt;/a&gt;, &lt;a href=&quot;https://github.com/facebook/zstd/tree/master/tests#paramgrill---tool-for-generating-compression-table-parameters-and-optimizing-parameters-on-file-given-constraints&quot;&gt;파라미터 최적화용 툴&lt;/a&gt; 등의 훌륭한 기능과 편의성을 지니고 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그 외&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호환성 등의 이유로 불가피하게 zlib를 써야 한다면, &lt;a href=&quot;https://github.com/ebiggers/libdeflate&quot;&gt;libdeflate&lt;/a&gt; 나 &lt;a href=&quot;https://github.com/cloudflare/zlib&quot;&gt;cloudflare/zlib&lt;/a&gt;를 사용해보자. 레퍼런스 구현인 기존 &lt;a href=&quot;https://github.com/madler/zlib&quot;&gt;zlib&lt;/a&gt;보다 최적화된 구현으로 잘 알려져 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간은 상관없고 그저 극한의 압축을 원한다면 &lt;a href=&quot;https://github.com/hxim/paq8px&quot;&gt;paq8px&lt;/a&gt;, &lt;a href=&quot;https://github.com/mathieuchartier/mcm&quot;&gt;mcm&lt;/a&gt; 같은 걸 쓰거나, 아예 해당 데이터셋에 최적화된 특수 목적 압축 알고리즘을 찾아보는 것도 고려해보자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;메모&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://raw.githubusercontent.com/andrew-aladev/brotli-vs-zstd&quot;&gt;참고 자료&lt;/a&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;References&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.gdcc.tech/results/&quot;&gt;GDCC - Global Data Compression Competition&lt;/a&gt; (2021)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://kirr.dyndns.org/sequence-compression-benchmark&quot;&gt;Sequence Compression Benchmark&lt;/a&gt; (2019 - 2022)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sites.google.com/site/powturbo/compression-benchmark&quot;&gt;TurboBench Compression Benchmark&lt;/a&gt; (2019)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://quixdb.github.io/squash-benchmark/&quot;&gt;Squash Compression Benchmark&lt;/a&gt; (2018)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://encode.su/threads/3315-enwik10-benchmark-results&quot;&gt;encode.su - enwik8 benchmark&lt;/a&gt; (2020)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://j.mearie.org/post/155896363363/compression-algorithm-renaissance-part-1&quot;&gt;메아리 저널 - 압축 알고리즘 르네상스 1&lt;/a&gt; (2017)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://j.mearie.org/post/155924859934/compression-algorithm-renaissance-part-2&quot;&gt;메아리 저널 - 압축 알고리즘 르네상스 2&lt;/a&gt; (2017)&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>cgiosy</author>
      <guid isPermaLink="true">https://cgiosy.tistory.com/9</guid>
      <comments>https://cgiosy.tistory.com/entry/%EC%95%95%EC%B6%95-%EC%86%8D%EB%8F%84%EB%B3%84-%EC%B5%9C%EC%A0%81-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98#entry9comment</comments>
      <pubDate>Thu, 7 Jul 2022 10:02:13 +0900</pubDate>
    </item>
    <item>
      <title>Z 컴비네이터 (Y 컴비네이터)</title>
      <link>https://cgiosy.tistory.com/entry/Z-%EC%BB%B4%EB%B9%84%EB%84%A4%EC%9D%B4%ED%84%B0-Y-%EC%BB%B4%EB%B9%84%EB%84%A4%EC%9D%B4%ED%84%B0</link>
      <description>&lt;p&gt;일급 함수 있는 언어에서 익명 재귀함수 만드는 테크닉. 재귀함수를 인자로 넘기는 상황이나 딱 한 번만 쓸 때 써봄직하다. 재귀 없는 언어에선 재귀 구현이 가능하게 해주나, 그런 언어는 안 쓰이니 실용성은 없다.&lt;/p&gt;
&lt;p&gt;Z 컴비네이터에 적당한 규격의 함수, 예를 들어 &lt;code&gt;Z(self =&amp;gt; n =&amp;gt; (n ? n + self(n - 1) : 0))&lt;/code&gt; 처럼 넣으면 n까지의 합을 구하는 익명 재귀함수를 만들 수 있다. 그래서 이걸 어떻게 만들까?&lt;/p&gt;
&lt;p&gt;우선 &lt;code&gt;f&lt;/code&gt;에서 자기자신을 호출하기 위해 스스로를 인자로 받아야 하므로, 첫 번째 인자인 &lt;code&gt;self&lt;/code&gt;를 &lt;code&gt;f&lt;/code&gt;로 채워주는 함수 &lt;code&gt;M = f =&amp;gt; f(f)&lt;/code&gt;를 선언한다. 그러면 &lt;code&gt;M(f)&lt;/code&gt;를 했을 때 &lt;code&gt;self&lt;/code&gt;에 &lt;code&gt;f&lt;/code&gt; 자신, &lt;code&gt;self =&amp;gt; n =&amp;gt; ...&lt;/code&gt; 부분이 들어갈 것이다. &lt;code&gt;f&lt;/code&gt;에선 &lt;code&gt;self(self)&lt;/code&gt;를 해서 &lt;code&gt;n =&amp;gt; ...&lt;/code&gt; 부분을 얻을 수 있으므로, 사실 이걸로 이미 익명 재귀함수가 만들어진다. 당황스러울 만큼 쉽지 않은가?&lt;/p&gt;
&lt;p&gt;다만 &lt;code&gt;self(self)&lt;/code&gt;를 안 하고 예시처럼 쓸 수 있으면 좋을 것이다. &lt;code&gt;f&lt;/code&gt; 대신 &lt;code&gt;M(f)&lt;/code&gt;를 주면 어떨까? &lt;code&gt;Z = f =&amp;gt; f(Z(f))&lt;/code&gt; 같이 말이다. 다만 이러면 재귀를 무한히 돌기 때문에, &lt;code&gt;Z(f)&lt;/code&gt; 호출은 실제로 얘를 써야 할 때까지 지연시켜야 한다. 이는 그냥 함수로 한 번 감싸주면 되며, &lt;code&gt;Z = f =&amp;gt; f((...args) =&amp;gt; Z(f)(...args))&lt;/code&gt;가 된다.&lt;/p&gt;
&lt;p&gt;코드가 살짝 길어지니 지연시키는 함수를 따로 빼보자. &lt;code&gt;W = f =&amp;gt; (...args) =&amp;gt; f(...args)(...args)&lt;/code&gt; 정도면 될 것이다. 이 함수는 &lt;code&gt;f&lt;/code&gt;가 반환한 함수에 인자를 되먹임하는데, 함수 호출을 지연시키는 것 외로도 여러 함수를 체이닝하는 데 쓸 수 있다. 아무튼 이를 활용하면 &lt;code&gt;Z = f =&amp;gt; f(W(_ =&amp;gt; Z(f)))&lt;/code&gt; 정도가 된다.&lt;/p&gt;
&lt;p&gt;기능 자체는 마무리됐지만, 아직 &lt;code&gt;Z&lt;/code&gt;가 익명이 아닌 재귀함수이기에 정의에 맞지 않는다. 다행히 우린 &lt;code&gt;M&lt;/code&gt;으로 임의의 재귀함수를 익명으로 바꿀 수 있음을 알고 있다 (&lt;code&gt;self(self)&lt;/code&gt;처럼 써야 한단 점이 아쉬웠을 뿐). 적당히 바꿔보면 최종적으로 &lt;code&gt;Z = M(z =&amp;gt; f =&amp;gt; f(W(_ =&amp;gt; z(z)(f))))&lt;/code&gt;다.&lt;/p&gt;
&lt;p&gt;이걸로 완성이다! 꽤 재밌지 않은가? 기회가 된다면 &lt;a href=&quot;https://github.com/loophp/combinator&quot;&gt;다른 다양한 컴비네이터들&lt;/a&gt;도 만들면서 놀아보면 좋을 것 같다.&lt;/p&gt;</description>
      <author>cgiosy</author>
      <guid isPermaLink="true">https://cgiosy.tistory.com/8</guid>
      <comments>https://cgiosy.tistory.com/entry/Z-%EC%BB%B4%EB%B9%84%EB%84%A4%EC%9D%B4%ED%84%B0-Y-%EC%BB%B4%EB%B9%84%EB%84%A4%EC%9D%B4%ED%84%B0#entry8comment</comments>
      <pubDate>Thu, 7 Jul 2022 09:59:42 +0900</pubDate>
    </item>
    <item>
      <title>LIS</title>
      <link>https://cgiosy.tistory.com/entry/LIS</link>
      <description>&lt;p&gt;&lt;a href=&quot;https://cgiosy.github.io/posts/lis&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://cgiosy.github.io/posts/lis&lt;/a&gt;&lt;/p&gt;</description>
      <category>DP</category>
      <category>lis</category>
      <category>알고리즘</category>
      <author>cgiosy</author>
      <guid isPermaLink="true">https://cgiosy.tistory.com/3</guid>
      <comments>https://cgiosy.tistory.com/entry/LIS#entry3comment</comments>
      <pubDate>Wed, 11 Dec 2019 15:52:08 +0900</pubDate>
    </item>
  </channel>
</rss>