<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>취미로 코딩하기</title>
    <link>https://measurezero.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sun, 5 Jul 2026 19:19:30 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>measurezero</managingEditor>
    <item>
      <title>[PS/CP 공부기록] 08. Degeneracy, Degeneracy Ordering, Counting 3-cycles, Counting 4-cycles</title>
      <link>https://measurezero.tistory.com/2379</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;※ 이 공부기록은 부정확한 내용이 다수 포함되어있을 수 있다. 또한 이 글은 정보 전달이 주 목적이 아닌 개인적인 기록용이어서 체계적으로 내용이 정리되어 있지 않을 수 있다. 읽는이의 양해를 바란다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;개요&lt;/span&gt;&lt;/h3&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/CP에서 주어지는 그래프 중 정점이 많이 주어지는(10만개 이상) 그래프는 대부분 sparse하다는 점을 의식해본 적이 있을 것이다. 시간 제한 내로 그래프가 dense해질 정도로 에지를 입력을 통해 많이 줄 수 없기 때문이다. 그렇다면 이 sparse하다는 점을 파고드는 문제해결전략도 있을 것이라는 생각이 이어서 든다.&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;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;이 글에서는 그래프의 밀도와 관련된 지표 중 하나인 deneneracy를 설명한다. 그리고 이와 관련된 degeneracy ordering을 소개하고, PS에서 이를 활용하는 해결할 수 있는 대표적인 문제인 counting 3-cycle과 counting 4-cycle 문제를 살펴본다.&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;이 내용을 이해한다고 바로 PS/CP 실력이 증가하거나 하지는 않겠지만, 이후에 그래프 관련 개념을 공부하거나 문제를 풀 때 조금 더 넓은 시야를 가지고 접근하는 등 도움이 될 것이라고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Degeneracy, \(k\)-core&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;\(G\)의 모든 subgraph의 &quot;degree가 가장 작은 정점&quot;의 degree가 \(k\) 이하일 때 \(G\)를 \(k\)-degenarate graph라 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 그래프 \(G\)가 주어질 때, \(G\)의 부분그래프 중 &quot;degree가 가장 작은 정점&quot;의 degree가 가장 큰 부분그래프가 존재할 것이다. 그러한 부분그래프의 크기를 \(G\)의 degeneracy라고 정의한다. 즉, \(G\)의 degeneracy는 \(G\)가 \(k\)-degenerate graph가 되게 하는 \(k\)의 최솟값이다. 또한 degree가 가장 작은 정점의 degree가 \(k\)인 \(G\)의 maximal한 connected subgraph들을 각각 \(k\)-core이라고 부른다.&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;degeneracy는 주어진 그래프에서 모든 정점의 degree가 degeneracy \(k\)이상인 부분(\(k\)-core)이 그래프에 있다는 것을 보여주는 값이므로, 이를 통해 그래프의 부분밀도에 대한 정보를 간접적으로 알 수 있다.&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;※주의: 당연하지만, \(k\)-core의 형태가 complete graph일 필요는 없다. complete graph의 형태가 아닌 \(k\)-core의 가능한 형태 중 하나로 regular graph가 있다.&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\)라면 이 그래프의 degeneracy는 \(O(\sqrt{E})\)이 됨을 관찰하자. degeneracy가 \(k\)인 그래프는 정점이 \(k+1\)개인 complete graph이므로 \(\frac{k(k+1)}{2}\)개의 간선이 필요하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Degeneracy Ordering, Coloring Number&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※여기서 말하는 coloring number은 chromatic number과 다른 개념이므로 유의하자.&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;그래프의 degeneracy를 계산하는 것은 어렵지 않다. 가장 degree가 작은 정점을 지우는 것을 반복하면서, 매 순간의 최소 degree 정점의 degree 중 최댓값을 구하는 찾으면 된다. 이해와 증명 모두 직관적이므로 자세한 설명은 생략한다. 이 과정에서 지워지는 정점의 순서를 degeneracy ordering이라고 한다.&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;degeneracy ordering의 특징은 각 정점에 대하여 이후에 등장하는 정점의 수가 전체 그래프의 degeneracy \(k\) 이하라는 것이다.&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;degeneracy ordering의 역순으로 각 정점을 지금까지 색칠된 인접 정점을 칠하는 데에 사용되지 않은 색을 칠해나가는 전략을 생각해보자. degeneracy ordering의 특징을 잘 생각해보면 색칠할 차례의 각 정점에 대하여 그보다 앞 순서에서 색칠된 인접한 정점의 개수는 많아야 \(k\)개이므로 많아야 \(k+1\)개의 색으로 인접한 정점의 색이 다르게 그래프의 정점을 색칠할 수 있음을 알 수 있다. 이러한 이유에서 \(k+1\)을 coloring number라고 부르기도 한다. 다만 이름이 chromatic number(인접한 정점의 색이 다르게 그래프의 정점을 칠하기 위해 필요한 색의 최소 개수)와 혼동되기 쉬우므로 이 이름으로 자주 부르지는 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Counting 3-cycles&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 degeneracy ordering을 활용하여 해결할 수 있는 문제인 counting 3-cycles를 소개한다. counting 3-cycles 문제는 다음과 같은 문제를 말한다.&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;정점이 \(V\)개이고 간선이 \(E\)개(self-loop, duplicate edge 없음)인 무방향그래프 \(G\)가 있다. 이 그래프에서 찾을 수 있는 3-cycle(세 개의 정점으로 구성된 cycle)은 총 몇 개인가?&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\) 또는 \(E\)가 10만 이상인 그래프가 입력으로 주어지면 위 문제를 간단한 방법으로 해결하기는 쉽지 않아보인다. 그러나 문제의 형태가 매우 단순하다 보니 다양한 풀이가 존재하는 문제이기도 하다. 이 글에서는 degeneracy ordering을 이용한 방법을 알아보자.&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;주어진 그래프가 DAG가 되게끔 각 간선에 방향을 임의로 부여해보자. 이 때, 원래 그래프에서의 3-cycle은 항상 (cycle 위에서) indegree가 2인 정점 \(A\), indegree와 outdegree가 각각 1인 정점 \(B\), outdegree가 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;그렇다면, 주어진 그래프의 모든 간선에 대하여 그 간선의 양 끝 정점과 (DAG에서) 인접한 정점을 찾는 것을 반복하는 것으로 각 cycle을 \(A\)와 \(B\)를 잇는 간선이 선택되었을 때 정확히 한 번 셀 수 있다.&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;이제, DAG를 degeneracy ordering을 활용하여 구성해보자. 각 간선의 방향을 degeneracy ordering 기준 먼저 오는 정점에서 나중에 오는 정점으로 향하게 설정하면, 각 정점의 outdegree는 그래프의 degeneracy \(k\) 이하가 된다. 또한 설명했듯이 \(k\)는 \(O(\sqrt{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;따라서 위에서 구성한 알고리즘은 degeneracy ordering을 활용하여 구성한 DAG에서 각 간선 선택의 경우의 수 \(E\)에 대하여 두 정점과 인접한 공통 정점집합 찾기를 각각 \(O(\sqrt{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;따라서 degeneracy ordering을 활용하면 counting 3-cycle 문제를 \(O(M\sqrt{M})\)에 해결할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Counting 4-cycles&lt;/h3&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-cycle의 경우와 비슷한 방법으로 counting 4-cycles 문제도 정의 및 해결할 수 있다. counting 4-cycles 문제는 다음과 같은 문제를 말한다.&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;정점이 \(V\)개이고 간선이 \(E\)개(self-loop, duplicate edge 없음)인 무방향그래프 \(G\)가 있다. 이 그래프에서 찾을 수 있는 4-cycle(네 개의 정점으로 구성된 cycle. 구성 간선의 집합이 다르면 다른 cycle이다.)은 총 몇 개인가?&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-cycle의 경우와 다르게, 4-cycle은 DAG 위에서도 다양한 형태로 나타날 수 있다. 특히, (원래 그래프에서의) 4-cycle은 (DAG에서의) indegree가 2인 정점의 개수가 1개일 수도 있고 2개일 수도 있다. 그러니 각 4-cycle을 한 번씩만 세려면 조금 더 구체적인 조건을 세워야 할 것이다.&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;이러한 조건을 세우는 방법 중 하나로 indegree가 2이면서 맞은편에 있는 정점의 degeneracy order보다 큰 order를 가지는 정점을 갖는 4-cycle만을 세는 것이 있다.&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-cycle은 그래프의 각 정점(이것이 indegree가 2인 찾고자 하는 정점의 맞은편 정점이다)으로부터 &quot;원래 그래프에서의&quot; 인접 정점을 찾고, 그 정점에서 이어서 DAG에서의 degeneracy order가 첫 정점보다 더 큰 인접 정점(이것이 indegree가 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;위 알고리즘에서, 무방향그래프에서의 인접 정점을 찾는 횟수는 \(O(M)\)이며 각 정점에서 인접 정점을 조사하는 과정은 \(O(\sqrt{M})\)이다. 따라서 이 알고리즘의 최종 시간복잡도는 \(O(M\sqrt{M})\)이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;여담&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서 소개한 내용 말고도, degeneracy ordering은 그래프의 모든 maximal clique를 효율적으로 탐색하는 알고리즘인 Bron-Kerbosch 알고리즘의 최적화에 쓰이는 등 다른 곳에서도 사용하는 개념이다. 언젠가 clique 관련된 글을 작성하게 된다면 같이 소개하지 않을까....&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-cycle과 4-cycle을 셀 때, degeneracy ordering이 아닌 다른 ordering을 이용하기도 한다. PS/CP에서 널리 쓰이는 ordering 중 하나로 각 정점들을 degree가 \(\sqrt{M}\) 이상인 정점과 미만인 정점으로 나누어 degree가 낮은 정점들에 낮은 order를 부여하고 degree가 높은 정점에 높은 order를 부여하는 방법이 있다. degree가 낮은 정점의 outdegree는 많아야 \(\sqrt{M}\)이 되며, degree가 높은 정점은 그래프에 많아야 \(O(\sqrt{M})\)개이므로 degree가 높은 정점의 outdegree 또한 많아야 \(\sqrt{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;특정 형태의 그래프의 개수를 세는 문제를 해결할 때, bitset을 활용한 최적화를 시도하는 것도 좋은 방법이 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;관련 문제&lt;/h3&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://www.acmicpc.net/problem/3528&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ3528] K-Graph Oddity&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;degeneracy ordering을 이용한 정점 채색법을 활용해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글의 흐름에서는 살짝 벗어나지만, 이 문제와 관련된 더 많은 내용을 알고 싶다면 Brooks' Theorem과 &amp;Delta;-coloring을 찾아보자.&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://www.acmicpc.net/problem/1762&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ1762] 평면그래프와 삼각형&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;planar graph의 degeneracy는 5 이하임을 증명하고, 이를 활용해 문제를 해결해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 degeneracy의 상한을 이용하면 \(O(M\sqrt{M})\)으로 풀리지 않을 정도로 \(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;&lt;a href=&quot;https://www.acmicpc.net/problem/33946&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ33946] 부도덕한 그래프 (Hard)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DAG에서의 세 정점이 이룰 수 있는 관계를 잘 생각해보자.&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://www.acmicpc.net/problem/32395&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ32395] 4-cycle (Hard)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4-cycle을 세는 기본 문제이다.&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://www.acmicpc.net/problem/2390&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ2390] ⎐&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4-cycle에 뭔가가 더 붙은 그래프도 세어보자.&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://www.acmicpc.net/problem/28200&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ28200] 4&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비슷한 방법으로 4-clique 또한 셀 수 있다.&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://www.acmicpc.net/problem/31185&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ31185] Ancient Magic Circle in Teyvat&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포제원리를 활용하면 4-anticlique 또한 셀 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고 자료&lt;/h3&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://en.wikipedia.org/wiki/Degeneracy_(graph_theory)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://en.wikipedia.org/wiki/Degeneracy_(graph_theory)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dl.acm.org/doi/10.1145/2402.322385&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dl.acm.org/doi/10.1145/2402.322385&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.cs.cornell.edu/courses/cs6241/2019sp/readings/Chiba-1985-arboricity.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.cs.cornell.edu/courses/cs6241/2019sp/readings/Chiba-1985-arboricity.pdf&lt;/a&gt; : 그래프의 밀도와 관련된 또다른 지표인 arboricity의 개념을 이용하여 3-cycle 및 4-cycle counting을 계산하는 방법을 소개한다. arboricity와 degereracy는 많아야 2배 차이나는 지표이므로, 전반적인 흐름은 degeneracy를 활용한 것과 크게 다르지 않다.&lt;/p&gt;</description>
      <category>CP자료모음</category>
      <category>3-cycle</category>
      <category>4-cycle</category>
      <category>coloring number</category>
      <category>degeneracy</category>
      <category>degeneracy ordering</category>
      <category>graph theory</category>
      <category>k-core</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2379</guid>
      <comments>https://measurezero.tistory.com/2379#entry2379comment</comments>
      <pubDate>Mon, 9 Mar 2026 10:00:00 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 8551 // C++] Blokada</title>
      <link>https://measurezero.tistory.com/2378</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;※ 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼 문제는 백준 8551번 문제인 Blokada이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/8551&quot;&gt;https://www.acmicpc.net/problem/8551&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;최소한 몇 개의 방향간선을 지워야 1번 정점에서 \(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;이 값은 Max-Flow Min-Cut Theorem에 의해 각 간선의 가중치를 1으로 가정할 때의 1번 정점에서 \(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;Dinic's Algorithm을 이용해 최대 유량을 구하고 문제를 해결하자. 모든 간선의 가중치가 1인 유량 그래프에서 Dinic's Algorithm의 시간복잡도는 \(O(\min(E\sqrt{E}, E\sqrt{V^{\frac{2}{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;아래는 제출한 소스코드이다.&lt;/p&gt;
&lt;pre id=&quot;code_1771814419893&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;bitset&amp;gt;
#include &amp;lt;queue&amp;gt;
#include &amp;lt;cstring&amp;gt;
using namespace std;

int ans, src, snk, N, M;
vector&amp;lt;int&amp;gt; G[10002];
bitset&amp;lt;10002&amp;gt; flow[10002];
int lv[10002], trn[10002];
queue&amp;lt;int&amp;gt; que;

bool dfs(int cur) {
    if (cur == snk) return 1;
    int sz = G[cur].size();
    for (int &amp;amp;i = trn[cur]; i &amp;lt; sz; i++) {
        int nxt = G[cur][i];
        if (lv[nxt] != lv[cur] + 1 || !flow[cur][nxt]) continue;
        if (dfs(nxt)) {
            flow[cur][nxt] = 0;
            flow[nxt][cur] = 1;
            return 1;
        }
    }
    return 0;
}

bool bfs() {
    memset(lv, 0, sizeof(lv));
    lv[src] = 1;
    que.push(src);
    while (!que.empty()) {
        int cur = que.front(); que.pop();
        for (auto &amp;amp;nxt:G[cur]) {
            if (lv[nxt] || !flow[cur][nxt]) continue;
            lv[nxt] = lv[cur] + 1;
            que.push(nxt);
        }
    }
    return lv[snk];
}

void dinic() {
    while (bfs()) {
        memset(trn, 0, sizeof(trn));
        while (dfs(src)) ans++;
    }
}

void addedge(int x, int y) {
    flow[x][y] = 1;
    G[x].emplace_back(y);
    G[y].emplace_back(x);
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; N &amp;gt;&amp;gt; M;
    src = 1, snk = N;
    while (M--) {
        int x, y; cin &amp;gt;&amp;gt; x &amp;gt;&amp;gt; y;
        addedge(x, y);
    }
    dinic();
    cout &amp;lt;&amp;lt; ans;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 8551</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2378</guid>
      <comments>https://measurezero.tistory.com/2378#entry2378comment</comments>
      <pubDate>Tue, 24 Feb 2026 10:00:44 +0900</pubDate>
    </item>
    <item>
      <title>[PS/CP 공부기록] 07. Sum Over Subsets DP, AND Convolution, OR Convolution</title>
      <link>https://measurezero.tistory.com/2377</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;※ 이 공부기록은 부정확한 내용이 다수 포함되어있을 수 있다. 또한 이 글은 정보 전달이 주 목적이 아닌 개인적인 기록용이어서 체계적으로 내용이 정리되어 있지 않을 수 있다. 읽는이의 양해를 바란다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;들어가기 전에&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sum Over Subsets DP, 줄여서 SOS DP는 PS/CP에서는 꽤 유명한 주제라고 생각한다. 이름값이 있는 알고리즘이다보니 글쓴이도 예전에 SOS 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;앞으로 계속 익힐 주제들과 비교했을 때 SOS DP는 더 미루기에는 너무 유명한 주제라는 생각이 들었다. 더 미루지 말고 이런 생각이 든 김에 SOS DP와 그 관련 주제를 공부해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Submask Enumeration&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;원소가 \(K\)개인 집합 \(S\)의 각 부분집합 \(A\)는 \(K\)개의 비트를 이용하여 각 원소와 대응되는 비트를 키고 끄는 것으로 모두 나타낼 수 있다. 만약 \(S\)의 모든 각 부분집합 \(m\)에 대하여 각 집합의 모든 부분집합 \(m'\)에 접근하는 알고리즘을 설계한다면 총 몇 개의 \(m'\)에 접근하게 될까?&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;단순하게 생각하면,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;부분집합의 개수가 가장 많은 \(S\)의 부분집합 \(m\)은 \(S\)이고 \(S\)의 부분집합의 개수는 \(2^K\)개이므로 일단 \(O(4^K)\)가 될 것이라고 생각할 수 있다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;그러나 실제로는 이보다 더 적은 \(3^K\)개의 \(m'\)에 접근하게 된다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;\(A\)의 원소 \(k\)개인 부분집합은 \(\binom{K}{k}\)개이며, 각 부분집합은 \(2^k\)개의 부분집합을 가진다. 따라서 \(A\)의 원소 \(k\)개인 부분집합에서는 총 \(\binom{K}{k}2^k\)개의 \(m'\)에 접근하게 된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이를 모든 \(k\)에 대해 합하면 이항정리(binomial theorem)에 의해 \(\sum_{k=0}^K \binom{K}{k}2^k=3^K\)가 된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;\(3^{17}\)이 129140163이므로, 제한시간이 넉넉하게 주어지는 문제의 경우 이러한 접근법으로 해결할 수도 있다.&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;참고로, bitmask를 활용하여 표현된 어떤 집합을 나타내는 정수\(x\)가 있을 때, \(x\)의 모든 부분집합은 초기값이 \(x\)인 변수 \(x'\)에 대하여 \(x':=(x'-1)\&amp;amp;x\)를 반복하는 것으로 모두 접근할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Sum Over Subsets DP&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;집합의 부분집합에 대한 몇몇 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;Sum Over Subsets DP는 원소가 \(K\)개인 집합 \(S\)의 모든 부분집합 \(m\)에 대하여 대하여 \(f(m)\)의 값이 알려져 있을 때 \(\sum_{m'\subseteq m} f(m')\)의 값들을 \(O(K2^K)\)에 계산하는 동적 계획법 알고리즘이다.&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;각 부분집합 \(m\)과 원소의 인덱스 \(k\)에 대하여 \(dp(m, k)\)를 \(m\)의 부분집합 \(m'\)중 \(k\)를 초과하는 인덱스의 원소의 포함 여부는 \(m\)과 상태가 동일한 집합에 대한 \(f(m')\)의 합으로 정의하자. 그리고 초기값으로 \(S\)의 각 부분집합 \(m\)에 대하여 \(dp(m, -1)\)을 \(f(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;\(dp(m, k)\)의 합을 구성하는 부분집합들을 인덱스 \(k\)의 원소를 포함하는 부분집합과 그렇지 않은 부분집합으로 나누어 생각해보자. &quot;인덱스 \(k\)의 원소를 포함하는 부분집합&quot;은 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;\(k\) 이상의 인덱스를 갖는 원소의 포함 여부가 \(m\)와 상태가 동일하다&lt;/span&gt;. 따라서 이 집합들의 \(f\)값의 합은 \(dp(m, k-1)\)와 같다. 그리고 &quot;그렇지 않은 부분집합&quot;의 \(f\)값의 합은 \(m\)에서 인덱스 \(k\)의 원소를 제거한 집합 \(m^*\)에 대하여 인덱스가 \(k\) 이상의 인덱스를 갖는 원소의 포함 여부가 \(m^*\)와 동일한 부분집합 \(m'\)들에 대한 \(f(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;위의 관계를 식으로 나타내면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;\(S\)의 부분집합 \(m\)에 대하여, \(m\)이 인덱스 \(k\)의 원소를 포함할 경우 \(dp(m, k) = dp(m^*, k-1) + dp(m,k-1)\)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;\(m\)이 인덱스 \(k\)의 원소를 포함하지 않을 경우 \(dp(m, k) = dp(m, k-1)\)&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;구하고자 하는 값은 \(dp(S,K-1)\)이므로, 이를 계산하려면 위 형태의 점화식을 \(O(K2^K)\)회 계산하면 된다. 위 점화식을 이루는 연산은 단순 덧셈이니 \(O(1)\)에 실행할 수 있다. 따라서 점화식을 이루는 각 집합에 대한 접근이 O(1)에 이뤄질 수 있으면 이 알고리즘의 시간복잡도는 \(O(K2^K)\)가 된다. 이는 비트마스킹을 이용하여 간단히 해낼 수 있다.&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;정확히 위의 문제를 해결하고자 하는 경우, \(k\)값에 대한 모든 \(dp\)값을 계산한다면 \(k-1\) 이하의 값은 더 이상 필요없어지므로, \(k\)에 대해 순차적으로 계산하면서 각 \(k\)에 대하여 집합들을 적절한 순서로 방문하는 것으로 \(O(2^K)\)의 메모리를 활용해 문제를 해결할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Sum Over Supersets DP&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sum Over Subsets DP는 원소가 \(K\)개인 집합 \(S\)의 모든 부분집합 \(m\)에 대하여 \(f(m)\)의 값이 알려져 있을 때 \(\sum_{m\subseteq m'\subseteq S} f(m')\)의 값들을 \(O(K2^K)\)에 계산하는 동적 계획법 알고리즘이다.&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;이 문제는 앞에서 살펴본 Sum Over Subsets DP와 완전히 동일한 방식으로 해결할 수 있다. 단순히 각 부분집합들을 \(S\)에 대한 여집합에 대응시키면 문제가 Sum Over Subsets DP와 동일해짐을 확인하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;OR Convolution&lt;/h3&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;두 수열 \(a_0, a_1, \cdots, a_{2^K-1}\)과&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;\(b_0, b_1, \cdots, b_{2^K-1}\)에 대하여 \(c_m=\sum_{i|j=m}a_ib_j\)을 만족하는 수열 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;\(c_0, c_1, \cdots, c_{2^K-1}\)를 정의하자. 여기에서 \(|\)은 두 정수의 bitwise OR 연산을 의미한다. 이 때 수열 \(c\)를 수열 \(a\)와 \(b\)의 OR Convolution이라고 한다.&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;편의를 위해, 집합 \(m'\)에 대하여 \(a_{m'}\)을 \(a\)의 &quot;\(m'\)에 대응되는 비트마스크&quot;번째 수로 정의하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 두 함수 \(A\)와 \(B\)를 \(A(m)=\sum_{m'\subseteq m} a_{m'}\), \(B(m)=\sum_{m'\subseteq m} b_{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;이 때, \(A(m)\cdot B(m)\)의 값은 \( \sum_{m'\subseteq m} a_{m'} \cdot \sum_{m'\subseteq m} b_{m'} \)이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 위의 값은 \(C(m)=\sum_{m'\subseteq m} c_{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;\(a_n\)과 \(b_n\)으로부터 \(A\)와 \(B\)를 얻는 것은 SOS DP로 어렵지 않게 해낼 수 있다. 그렇다면 \(C\)로부터 \(c_n\)을 얻는 것은 가능할까? \(c_n\)으로부터 \(C\)를 얻는 것은 SOS DP를 통해 어렵지 않게 할 수 있으므로, 이를 거꾸로 수행하면 \(C\)로부터 \(c_n\)을 얻을 수 있다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AND Convolution&lt;/h3&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;두 수열 \(a_0, a_1, \cdots, a_{2^K-1}\)과&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;\(b_0, b_1, \cdots, b_{2^K-1}\)에 대하여 \(c_m=\sum_{i\&amp;amp;j=m}a_ib_j\)을 만족하는 수열&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;\(c_0, c_1, \cdots, c_{2^K-1}\)를 정의하자. 여기에서 \(\&amp;amp;\)은 두 정수의 bitwise AND 연산을 의미한다. 이 때 수열 \(c\)를 수열 \(a\)와 \(b\)의 AND Convolution이라고 한다.&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;AND Convolution 역시 위의 OR Convolution과 동일한 원리로 수행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Subset Convolution&lt;/h3&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;\(K\)값이 크지 않다면, 몇몇 문제는 \(O(K^22^K)\) 꼴의 시간복잡도 풀이를 갖기도 한다. 그 예시로 Subset Convolution을 소개한다.&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;두 수열 \(a_0, a_1, \cdots, a_{2^K-1}\)과&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;\(b_0, b_1, \cdots, b_{2^K-1}\)에 대하여 \(c_m=\sum_{i|j=m, i\&amp;amp;j=0}a_ib_j\)을 만족하는 수열&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;\(c_0, c_1, \cdots, c_{2^K-1}\)를 정의하자. 여기에서 \(|\)은 두 정수의 bitwise OR 연산을, \(\&amp;amp;\)은 두 정수의 bitwise AND 연산을 의미한다. 이 때 수열 \(c\)를 수열 \(a\)와 \(b\)의 Subset Convolution이라고 한다.&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;Subset Convolution이 OR Convolution과 다른 점은 \(i\&amp;amp;j=0\) 조건이 하나 추가된 것이 전부임을 확인하자. 이 점을 이용하면 Subset Convolution을 \(O(K^22^K)\)에 계산할 수 있다.&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;\(A(m, k)\)와 \(B(m,k)\)를 1인 비트의 개수가 \(k\)개인 \(m\)으로 나타나는 항의 값만을 이용한(나머지 항은 0으로 취급) \(a\)와 \(b\)에 대한 SOS DP의 결과라 하자. 그리고 \(C(m, k)=\sum_{k'=0}^{k}A(m,k')\cdot B(m,k-k')\)과 같이 정의하자. 이렇게 \(C\)를 정의하면 \(k\)가 \(m\)의 1인 비트 개수와 정확히 같을 때 \(A\)와 \(B\)에서 겹치는 비트가 없어야지만 OR 값으로 \(m\)이 정확히 나오게 강제할 수 있으므로, 각 \(k\)에 대하여 SOS DP의 undo하는 과정을 거치면 각 \(m\)에 대하여 \(m\)의 1인 비트 개수 \(k\)에 대해 (undo된) 값을 확인하는 것으로 Subset Convolution의 값을 모두 얻을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;여담&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bitmasking 문제를 만난다면 먼저 최대한 문제를 단순한 형태로 정리해보고, 두 mask 사이의 subset 관계를 활용하기 좋은 형태일 때 이 글의 내용을 풀이법 후보로 생각해보는 것이 좋다. 한편, mask 사이의 포함관계로 문제를 정리할 수 있는 경우더라도, Held-Karp Algorithm(bitmask TSP)이나 각 bitmask를 정점으로 하는 (방향)그래프에서의 BFS 등 더 쉽고 직관적인 방법을 활용할 수 있는지도 생각해보는 것이 좋다. 쉬운 방법으로도 문제를 해결할 수 있다면, 특히 대회에서는 문제를 굳이 어려운 방법으로 해결할 필요는 없다.&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;s&gt;(그림 없이 다차원 누적합을 묘사하기 귀찮아서 생략했다)&lt;/s&gt;, SOS DP를 \(K\)차원의 누적 합과 차이배열로 설명하기도 한다. 이 또한 흥미로운 내용이므로 알아두면 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;관련 문제&lt;/h3&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;이 글에서 Submask Enumeration을 설명하기는 했으나, 굳이 SOS DP로 안 풀어도 되는 제한의 문제를 SOS DP로 해결하는 것을 막으려는 목적으로 설명을 한 것이다. 또한 이 글에서 Subset Convolution을 설명하기는 했으나, 어디까지나&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;\(O(K^22^K)\)의 문제풀이 예시로 보여주기 위해서 설명했을 뿐 그 활용은 이 글의 범위를 넘는다.&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;아래의 문제들은 SOS DP, AND Convolution, OR Convolution과 관련된 문제들이다. 몇몇 문제는 Generating Function(생성함수)의 개념이 익숙하지 않다면 어려울 수 있다.&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://www.acmicpc.net/problem/25563&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ25563] AND, OR, XOR&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자기 자신에 대한 AND Convolution과 OR Convolution으로 AND와 OR의 경우를 해결할 수 있다. XOR의 경우는 간단하므로 생략한다.&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://www.acmicpc.net/problem/13759&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ13759] &amp;amp;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sum Over Superset 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;&lt;a href=&quot;https://www.acmicpc.net/problem/23949&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ23949] Shifts&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각의 guard에 대하여, 각 날 하루를 일했을 때의 \(H\)값을 초기값으로 넣고 Sum Over Subsets 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;&lt;a href=&quot;https://www.acmicpc.net/problem/2803&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ2803] 내가 어렸을 때 가지고 놀던 장난감&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 장난감 상자는 사용하지 않거나 사용하므로, 각각 장난감의 사용 여부로 Sum Over Subsets DP를 한다면 convolution을 이용하여 문제를 해결할 수 있을 것이다. 그러나 각 장난감에 대하여 Sum Over Subsets DP를 사용하는 것은 당연히 무리다. 이를 최적화할 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/33102&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ33102] Counting Pairs&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;조건을 만족하려면 각 비트의 상태가 어때야 하는지를 잘 생각해보자(1)&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://www.acmicpc.net/problem/18719&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ18719] Binomial&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건을 만족하려면 각 비트의 상태가 어때야 하는지를 잘 생각해보자(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;a href=&quot;https://www.acmicpc.net/problem/30966&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ30966] 관심사&lt;/a&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;a href=&quot;https://www.acmicpc.net/problem/27574&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ27574] Digits of Unity&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SOS 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;&lt;a href=&quot;https://www.acmicpc.net/problem/25390&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ25390] Traveling Junkman Problem&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 풀이의 부분문제로 SOS 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;&lt;a href=&quot;https://www.acmicpc.net/problem/27841&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ27841] Problem Setting&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주어진 전체집합의 부분집합을 이용해 만들 수 있는 chain의 경우의 수를 구해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고 자료&lt;/h3&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://codeforces.com/blog/entry/45223&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://codeforces.com/blog/entry/45223&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://usaco.guide/plat/dp-sos&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://usaco.guide/plat/dp-sos&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://qwerasdfzxcl.tistory.com/35&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://qwerasdfzxcl.tistory.com/35&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://37zigen.com/subset-convolution/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://37zigen.com/subset-convolution/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/blog/view/127&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/blog/view/127&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://codeforces.com/blog/entry/92153&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://codeforces.com/blog/entry/92153&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://codeforces.com/blog/entry/115438&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://codeforces.com/blog/entry/115438&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>CP자료모음</category>
      <category>AND Convolution</category>
      <category>Bitmask</category>
      <category>bitmask dp</category>
      <category>dynamic programming</category>
      <category>OR Convolution</category>
      <category>SOS DP</category>
      <category>Subset Convolution</category>
      <category>Subset Enumeration</category>
      <category>Sum Over Subsets DP</category>
      <category>Sum Over Subsets Dynamic Programming</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2377</guid>
      <comments>https://measurezero.tistory.com/2377#entry2377comment</comments>
      <pubDate>Fri, 20 Feb 2026 10:00:53 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 34316 // C++] 사각형 개수 세기</title>
      <link>https://measurezero.tistory.com/2376</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;※ 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼 문제는 백준 34316번 문제인 사각형 개수 세기이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/34316&quot;&gt;https://www.acmicpc.net/problem/34316&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;\(NM\le10^5\) 제약조건을 눈여겨보자. 이 조건으로부터 \(\min(N,M)\le\sqrt{10^5}\)임을 알 수 있다. 이러한 형태로 문제에서 주어지는 입력에 추가적인 제한을 주는 경우는 제법 자주 나타나므로 잘 알아두는 것이 좋다.&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\)과 \(M\) 중 더 작은 값을 행의 개수로 하고, 두 개의 행을 골라 각 열의 두 행의 수의 합을 각각 구하는 작업의 시간복잡도는 \(O(\min(N,M)^2\max(N,M))=O(\min(N,M)NM)\)과 같다. \(NM\le10^5\)와 \(\min(N,M)\le\sqrt{10^5}\)가 성립하므로, 이와 같은 접근의 연산량은 1초 안에 충분히 수행 가능하는 점을 관찰할 수 있다. 여기까지 관찰했다면 이후의 계산은 어렵지 않을 것이다.&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;pre id=&quot;code_1770738362978&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;cstring&amp;gt;
using namespace std;
typedef long long ll;

int R, C;
vector&amp;lt;int&amp;gt; v[316];
ll ans;
int cnt[20];

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; R &amp;gt;&amp;gt; C;
    if (R &amp;lt; C) {
        for (int r = 0; r &amp;lt; R; r++) {
            for (int c = 0; c &amp;lt; C; c++) cin &amp;gt;&amp;gt; v[r].emplace_back();
        }
    }
    else {
        for (int r = 0; r &amp;lt; R; r++) {
            for (int c = 0; c &amp;lt; C; c++) {
                cin &amp;gt;&amp;gt; v[c].emplace_back();
            }
        }
        swap(R, C);
    }

    for (int r1 = 0; r1 &amp;lt; R; r1++) {
        for (int r2 = r1 + 1; r2 &amp;lt; R; r2++) {
            memset(cnt, 0, sizeof(cnt));
            for (int c = 0; c &amp;lt; C; c++) {
                cnt[v[r1][c] + v[r2][c]]++;
            }
            for (int i = 2; i &amp;lt; 10; i++) ans += cnt[i] * cnt[20 - i];
            int v1 = cnt[10], v2 = v1 - 1;
            if (v1 &amp;amp; 1) v2 /= 2;
            else v1 /= 2;
            ans += v1 * v2;
        }
    }
    cout &amp;lt;&amp;lt; ans;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 34316</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2376</guid>
      <comments>https://measurezero.tistory.com/2376#entry2376comment</comments>
      <pubDate>Wed, 11 Feb 2026 10:00:30 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 33922 // C++] 책 쌓기</title>
      <link>https://measurezero.tistory.com/2375</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;※ 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼 문제는 백준 33922번 문제인 책 쌓기이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/33922&quot;&gt;https://www.acmicpc.net/problem/33922&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;먼저, 주어진 책들을 가로가 세로 길이 이상인 방향으로 일괄적으로 돌려 생각해도 충분하다는 점을 관찰하자. 가로가 세로 길이 이상인 책 위에 세로가 가로 길이 이상인 책을 얹을 수 있다면 문제 제약에 의해 위의 책을 회전시킨 책도 올릴 수 있으며, 이것이 그 위로 새로 올릴 수 있는 책의 집합을 바꾸지 않기 때문이다.&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;주어진 책들의 몇몇 쌍에 대하여, 어떤 책을 다른 책 위에 올릴 수 있다는 관계가 성립하며, 이 관계는 poset을 이룬다. 따라서 이 문제는 이 책들의 집합의 poset에서의 최대 antichain의 크기를 구하는 것으로 해결할 수 있다. 같은 antichain에 속한 두 원소는 항상 서로 비교가 불가능해야 하므로, 책을 적절하게 뽑아 적절히 나열했을 때 가로의 길이가 strict하게 증가할 때 세로의 길이 또한 strict하게 증가하게끔 뽑을 수 있은 최대 책의 개수를 구하면 문제가 해결된다.&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;위의 문제는 LIS를 구하는 것으로 해결할 수 있다.&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;pre id=&quot;code_1770604100740&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;algorithm&amp;gt;
#include &amp;lt;cstring&amp;gt;
using namespace std;

int N, ans;
int A[200001];
pair&amp;lt;int, int&amp;gt; P[200001];

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; N;
    for (int i = 1; i &amp;lt;= N; i++) {
        cin &amp;gt;&amp;gt; P[i].first &amp;gt;&amp;gt; P[i].second;
        if (P[i].first &amp;lt; P[i].second) swap(P[i].first, P[i].second);
    }
    sort(P + 1, P + N + 1, greater&amp;lt;&amp;gt;());
    memset(A, 0x3f, sizeof(A));
    A[0] = 0;
    for (int i = 1; i &amp;lt;= N; i++) {
        int L = 0, R = i;
        while (L &amp;lt; R) {
            int mid = (L + R) / 2 + 1;
            if (A[mid] &amp;gt;= P[i].second) R = mid - 1;
            else L = mid;
        }
        A[L + 1] = min(A[L + 1], P[i].second);
    }
    ans = N;
    while (A[ans] == 0x3f3f3f3f) ans--;
    cout &amp;lt;&amp;lt; ans;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 33922</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2375</guid>
      <comments>https://measurezero.tistory.com/2375#entry2375comment</comments>
      <pubDate>Tue, 10 Feb 2026 10:00:14 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 33921 // C++] 아름다운 수열</title>
      <link>https://measurezero.tistory.com/2374</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;※ 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼 문제는 백준 33921번 문제인 아름다운 수열이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/33921&quot;&gt;https://www.acmicpc.net/problem/33921&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;전형적인 0-1 Fractional Programming 문제이다. 0-1 Fractional Programming에 대한 설명은 다음 공부 일지에 서술하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://measurezero.tistory.com/2371&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://measurezero.tistory.com/2371&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1770524311748&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[PS/CP 공부기록] 05. 0-1 Fractional Programming, Dinkelbach's Algorithm&quot; data-og-description=&quot;※ 이 공부기록은 부정확한 내용이 다수 포함되어있을 수 있다. 또한 이 글은 정보 전달이 주 목적이 아닌 개인적인 기록용이어서 체계적으로 내용이 정리되어 있지 않을 수 있다. 읽는이의 양&quot; data-og-host=&quot;measurezero.tistory.com&quot; data-og-source-url=&quot;https://measurezero.tistory.com/2371&quot; data-og-url=&quot;https://measurezero.tistory.com/2371&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/xiTwZ/dJMb86OXtaI/wLloRvOoLrN1SXy1syfvmK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/rdtrn/dJMb82eIGvt/wSPgVtmkLMtRYnjZeQgTu1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://measurezero.tistory.com/2371&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://measurezero.tistory.com/2371&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/xiTwZ/dJMb86OXtaI/wLloRvOoLrN1SXy1syfvmK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/rdtrn/dJMb82eIGvt/wSPgVtmkLMtRYnjZeQgTu1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[PS/CP 공부기록] 05. 0-1 Fractional Programming, Dinkelbach's Algorithm&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;※ 이 공부기록은 부정확한 내용이 다수 포함되어있을 수 있다. 또한 이 글은 정보 전달이 주 목적이 아닌 개인적인 기록용이어서 체계적으로 내용이 정리되어 있지 않을 수 있다. 읽는이의 양&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;measurezero.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;글쓴이는 구현 연습 겸 이 문제를 Dinkelbach's Algorithm으로 해결했다. 계산 과정에서 64비트 정수 자료형의 오버플로우가 발생할 수 있으므로, 구현 과정에서 128비트 정수 자료형을 사용했다.&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;pre id=&quot;code_1770523930597&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
using namespace std;
typedef long long ll;
typedef __int128 lll;

ll N, K, X, Y = 1, XX, YY;
ll A[100001], B[100001];

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; N &amp;gt;&amp;gt; K;
    for (int i = 1; i &amp;lt;= N; i++) {
        cin &amp;gt;&amp;gt; B[i]; A[i] = B[i] * B[i];
        A[i] += A[i - 1], B[i] += B[i - 1];
    }
    XX = A[N], YY = B[N];

    while ((lll)X * YY != (lll)Y * XX) {
        X = XX, Y = YY;
        lll val = 0, mx = 0; int qL = 0, qR = 0;
        for (int i = 0, lft = 0; i &amp;lt;= N - K; i++) {
            val += (lll)(A[i] - A[i - 1]) * Y - (lll)(B[i] - B[i - 1]) * X;
            if (val &amp;lt; 0) val = 0, lft = i + 1;
            lll v = val + (lll)(A[i + K] - A[i]) * Y - (lll)(B[i + K] - B[i]) * X;
            if (v &amp;gt; mx) mx = v, qL = lft, qR = i + K;
        }
        if (mx) XX = A[qR] - A[qL - 1], YY = B[qR] - B[qL - 1];
    }

    cout &amp;lt;&amp;lt; X / Y &amp;lt;&amp;lt; '.'; X %= Y;
    for (int k = 0; k &amp;lt; 10; k++) {
        X *= 10;
        cout &amp;lt;&amp;lt; X / Y;
        X %= Y;
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 33921</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2374</guid>
      <comments>https://measurezero.tistory.com/2374#entry2374comment</comments>
      <pubDate>Mon, 9 Feb 2026 10:00:44 +0900</pubDate>
    </item>
    <item>
      <title>[PS/CP 공부기록] 06. Maximum Closure Problem, Project Selection Problem, Maximum Weight Independent Set, 燃やす埋める問題</title>
      <link>https://measurezero.tistory.com/2373</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;※ 이 공부기록은 부정확한 내용이 다수 포함되어있을 수 있다. 또한 이 글은 정보 전달이 주 목적이 아닌 개인적인 기록용이어서 체계적으로 내용이 정리되어 있지 않을 수 있다. 읽는이의 양해를 바란다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;들어가기 전에&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글쓴이가 지금까지 참가한 두 오프라인 대회(Good Bye BOJ 2025, Semi Game Cup 4)에 공통적으로 출제된 주제가 있다. 바로 Maximum Flow Minimum Cut Theorem(최대 유량 최소 컷 정리)이다. 이 정리를 활용하는 문제들은 유량 그래프만 제대로 구성하면 그냥 Dinic's Algorithm 등 최대 유량을 구하는 알고리즘을 돌리는 것으로 해결할 수 있다. 따라서 이 유형의 난이도는 유량 그래프의 구성 난이도와 직결된다.&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;어려운 mincut 문제일수록 주어진 문제 상황을 적절한 유량 그래프로 모델링하는 것은 점점 어려워진다. 심지어 몇몇 문제는 겉으로만 보면 이게 그래프를 써야 하는 문제라는 것을 알기조차 어렵게 서술되어 있기도 하다. 또한, 뭔가 유량으로 풀릴 것 같다는 점을 문제의 다른 부분(입력제한이 작다는 사실 등)으로부터 짐작하더라도, 대회중에 밑바닥부터 유량 그래프를 설계하기에는 시간이 많이 들기도 한다. 그러나 시간이 지나면서 많은 mincut 기출문제가 생겼고, 그중 일부는 같은 원리나 모델링으로 묶을 수 있게 되었다.&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;이번 글에서는 이름이 붙은 몇몇 mincut 관련 문제들을 정리한다. 이 글에서 소개하는 이름있는 문제들의 해결법의 아이디어를 이해한다면 이들을 적절하게 활용하여 (모든 문제는 아니지만) 다양한 mincut 문제들을 해결할 수 있을 것이다.&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;이 글은 mincut이 무엇인지부터 다루지는 않는다. 만약 mincut을 모른다면 따로 공부하고 오자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Maximum Closure Problem&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정점의 집합이 \(V\)인 방향그래프 \(G\)의 정점의 부분집합 \(C\)가 &lt;b&gt;Closure&lt;/b&gt;이라는 것은 \(G\)의 간선을 따라 \(C\)의 정점으로부터 도달할 수 있는 모든 정점의 집합이 \(C\)가 된다는 것을 의미한다. &lt;b&gt;Maximum Closure Problem&lt;/b&gt;은 각 정점에 가중치가 있는 방향그래프 \(G\)의 closure 중 정점의 가중치의 합의 최댓값 또는 그 값을 갖게 하는 closure을 찾는 문제이다. 이 때, 정점의 가중치는 양수와 음수 모두 주어질 수 있다. (음이 아닌 정수만 주어지면 그냥 \(G\)를 고르면 되니 의미없는 문제가 된다.)&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;이 문제는 다음과 같이 유량 그래프를 구성하는 것으로 mincut으로 해결할 수 있다.&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) source 정점 \(S\)와 sink 정점 \(T\)를 준비한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2) \(V\)의 정점 중 가중치가 양수 \(w\)인 정점은 \(S\)에서 해당 정점으로 이어지는 최대 용량 \(w\)의 간선을, 음수 \(-w\)인 정점은 해당 정점에서 \(T\)로 이어지는 최대 용량 \(w\)의 간선을 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(3) \(G\)에 원래 있던 간선의 최대 용량을 (가상의) 무한대로 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(4) (\(V\)의 정점 중 가중치가 양수인 정점의 가중치의 합)에서 위에서 구성한 유량 그래프의 mincut의 크기를 뺀 것이 Maximum Closure의 가중치가 되며, 그 때의 Closure 중 하나는 유량 그래프에서의 mincut을 하나 구했을 때의 \(S\)와 연결된 정점들의 집합이 된다.&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;각 closure과 위의 유량 그래프에서의 각 cut은 대응되며, 각 cut의 가중치의 합은 closure에서는 (가중치가 양수인 정점 중 closure에 포함되지 않은 정점의 가중치의 합)에서 (가중치가 음수인 정점 중 closure에 포함된 정점의 가중치의 합)을 뺀 것과 같다. 한편, 실제 closure의 가중치의 합은 (가중치가 양수인 정점의 가중치의 합)에서 위의 값을 뺀 것과 같다. (가중치가 양수인 정점의 가중치의 합)은 고정된 값임을 관찰하는 것으로 mincut을 찾으면 maximum closure 또한 구할 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Project Selection Problem&lt;/h3&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;b&gt;Project Selection Problem&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;\(N\)개의 프로젝트와 \(M\)개의 기계가 있다. 각 프로젝트를 마치면 수익이 발생하고, 각 기계를 구입하면 비용이 발생한다. 그리고 각 프로젝트마다 진행에 필요한 기계의 집합이 있으며 그 기계를 구입하지 않으면 해당 프로젝트를 진행할 수 없다. 각 프로젝트는 최대 한 번 진행할 수 있으며, 기계는 한 번 구입하면 그 기계가 필요한 모든 프로젝트에 활용할 수 있다.&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;이 문제는 각 프로젝트와 기계 사이의 관계를 간선으로 하는 그래프의 Maximum Closure Problem으로 모델링할 수 있다. 즉, A번 프로젝트를 진행하려면 B번 기계가 필요한 관계를 A에서 B로 향하는 방향간선으로 표현하면, 주어진 문제는 Maximum Closure Problem으로 해결할 수 있음을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Maximum Weighted Independent Set of Bipartite Graph&lt;/h3&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;b&gt;Maximum Weighted Independent Set of Bipartite Graph&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;이분그래프(Bipartite graph) \(G\)가 주어진다. 각 \(G\)의 정점에 양의 가중치가 주어질 때, 가중치의 합이 최대가 되게 하는 independent set과 그 때의 가중치를 구하여라.&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;bipartite graph에서의 Maximum Weighted Independent Set은 Minimum Weighted Vertex Cover의 여집합과 같다. (Bipartite Matching에서의 둘의 관계를 생각하면 당연하다.) 따라서 Maximum Weighted Independent Set은 Minimum Weighted Vertex Cover을 구하는 것으로 구할 수 있다.&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;Minimum Weighted Vertex Cover은 다음과 같이 유량 그래프를 구성하는 것으로 mincut으로 해결할 수 있다. 단, \(G\)의 정점의 집합이 \(L\)과 \(R\)의 두 집합으로 분할된다고 가정하자.&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) source 정점 \(S\)와 sink 정점 \(T\)를 준비한다.&lt;br /&gt;(2) \(L\)의 정점은 \(S\)에서 해당 정점으로 이어지는 해당 정점의 가중치만큼의 용량을 가진 간선을, \(R\)의 정점은 해당 정점에서 \(T\)로 이어지는 해당 정점의 가중치만큼의 용량을 가진 간선을 추가한다.&lt;br /&gt;(3) \(G\)에 원래 있던 간선의 최대 용량을 (가상의) 무한대로 설정한다.&lt;br /&gt;(4) 위에서 구성한 유량 그래프의 mincut의 크기가 Minimum Weighted Vertex Cover의 크기가 되며, 해당 cover에 포함되지 않는 정점의 집합이 Maximum Weighted Independet Set이 된다.&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;각 vertex cover와 위의 유량 그래프에서의 cut은 대응되며, 따라서 이 그래프에서 mincut을 구하면 자연스럽게 Minimum Weighted Vertex Cover을 구하게 됨을 관찰하자.&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;여담으로, Maximum Weighted Independent Set 문제는 general graph에서는 NP-Hard이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;燃やす埋める 문제&lt;/h3&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;b&gt;燃やす埋める 문제&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;처리해야하는 \(N\)개의 쓰레기가 있다. 주어진 쓰레기를 처리하는 방법에는 소각하는 것과 매립하는 것의 두 가지가 있고, \(i\)번째 쓰레기 \(a_i\)를 소각해 처리하면 \(x_i\)의 비용이, 매립해 처리하면 \(y_i\)의 비용이 발생한다. 또한 \(M\)개의 쓰레기의 순서쌍 \(C_i = (a_j,a_k)\)에 대하여, \(a_j\)를 소각하여 처리하고 \(a_k\)를 매립하여 처리하면 추가로 \(c_i\)의 비용이 발생한다. 모든 쓰레기를 처리하는 데에 필요한 최소 비용을 구하여라.&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;燃やす埋める 문제는 다음과 같이 유량 그래프를 구성하는 것으로 mincut으로 해결할 수 있다.&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)&amp;nbsp;source&amp;nbsp;정점&amp;nbsp;\(S\)와&amp;nbsp;sink&amp;nbsp;정점&amp;nbsp;\(T\)를&amp;nbsp;준비한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2) 각 쓰레기 \(a_i\) 정점에 대하여, \(S\)에서 \(a_i\)로 이어지는 용량 \(y_i\)의 간선과 \(a_i\)에서 \(T\)로 이어지는 용량 \(x_i\)의 간선을 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(3) 각 순서쌍 \(C_i=(a_j,a_k)\)에 대하여, \(a_j\)에서 \(a_k\)로 이어지는 용량 \(c_i\)의 간선을 추가한다.&lt;br /&gt;(4) 위에서 구성한 유량 그래프의 mincut의 크기가 燃やす埋める 문제의 답이 된다.&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;(2)에서 \(y_i\)와 \(x_i\)의 순서는 바뀐 것이 아님에 유의하자.&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;각 쓰레기의 처리 방식을 정하면 그에 대응되는 위의 유량 그래프의 cut이 항상 존재한다. 정확히는, 각 쓰레기의 처리 방식을 정하면 그 선택에 따라 발생하는 추가 비용 \(c_i\)들이 발생하며, 이에 대응되는 간선들을 모두 고르는 것이 유량 그래프의 cut이 된다. 한편, 위 그래프의 mincut을 구성하는 간선에는 (\(C_i\)에 대응되는 간선을 제외하고 봤을 때) 각 쓰레기별로 소각과 매립에 대응되는 간선이 정확히 하나씩 들어가는데, 그 이유는 다음의 두 가지 관찰로부터 알 수 있다.&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;어떤 쓰레기에 대하여 소각과 매립에 대응되는 간선을 하나도 넣지 않으면 그 둘에 대응되는 간선으로 여전히 추가적인 유량을 흘릴 수 있으므로 둘 중 적어도 하나는 cut에 포함되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mincut이 둘을 모두 고른다고 가정하면 이 쓰레기는 mincut에 의해 분리된 \(S\)쪽 그래프와 \(T\)쪽 그래프 중 하나에 속하게 되며, 그 중 같은 쪽에 속한 간선을 지워도 여전히 cut이 되므로 모순이 발생한다.&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;따라서 위의 유량 그래프의 mincut은 각 쓰레기의 처리 방식을 고르는 방법 중 하나에 항상 대응된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;여담&lt;/h3&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;문제에 따라서 따라 풀이에 쓰이는 유량 그래프 모델링이 유일하지 않은 경우도 많다. 위의 여러 이름있는 유형의 문제를 참고하여 주어지는 문제에 따라 자유롭게 유량 그래프를 구성할 수 있게 되어보자.&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;mincut 문제유형에 익숙한 읽는이에게 이 글은 비슷한 내용을 계속 반복설명하는 듯한 인상을 줄 수도 있을 것이다. 실제로 위의 내용 중 몇몇은 일부만 알아도 이를 활용하여 다른 몇몇을 해결할 수 있기도 하다. 그럼에도 위의 모든 문제들을 서로 다른 문제인 것 처럼 따로 서술한 이유는 (물론 글쓴이가 공부한 내용을 정리하기 위함도 있지만) 읽는이에게 상대적으로 더 잘 이해되고 더 잘 다가오는 mincut 문제 풀이법과 모델링을 찾기 좋게 하기 위해서이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;관련 문제&lt;/h3&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;&lt;a href=&quot;https://www.acmicpc.net/problem/15273&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ15273]&amp;nbsp;Open-Pit&amp;nbsp;Mining&lt;/a&gt;, &lt;a href=&quot;https://www.acmicpc.net/problem/18771&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ18771] 오픈소스 버그 잡기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;Closure Problem 기본 문제이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/19579&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ19579] 물건 가져가기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 문제와 같지만 결과로 얻는 closure까지 직접 구해야 한다.&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://www.acmicpc.net/problem/8402&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ8402] Travel Agency&lt;/a&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;a href=&quot;https://www.acmicpc.net/problem/12936&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ12936] 영웅은 죽지 않아요&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;두 영웅이 같이 부활하면 에너지를 되돌려주는 관계&quot;를 의미하는 가상의 정점을 추가하고, 이 정점과 \(S\) 및 그 대응되는 각 영웅 사이의 관계를 간선으로 잘 표현해보자.&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://www.acmicpc.net/problem/35036&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ35036] 코드배틀&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;특정 한 팀이 계속 이기는 상황을 밑바탕으로 깔고, 여기에서 다른 한 팀이 이기는 턴을 어떻게 고를지를 최적화하는 문제&quot;로 생각해보자. 그리고 각 턴이 끝날 때마다의 점수 상황만을 보고 얻는 흥미도를 제외한 다른 흥미도를 얻을 수 있는 방법에 대하여, 각 방법을 의미하는 가상의 노드를 새로 추가하고 이 노드와 \(S\), \(T\), 그 방법에 대응되는 턴 사이의 관계를 간선으로 잘 표현해보자.&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://www.acmicpc.net/problem/31150&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ31150] Flood Fill&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0과 1로 채워진 격자 그래프에서의 connected component들끼리의 연결관계는 bipartite graph를 이룬다는 점을 잘 활용해보자. 또한, 모든 칸에 대하여 각 칸의 수가 두 번 이상 뒤집히지 않고도 모든 가능한 최종 색 배열을 완성시킬 수 있다는 관찰을 활용하자.&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://www.acmicpc.net/problem/35189&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ35189] X 칠하기 게임&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;격자의 각 칸을 체스판의 색 배열과 같이 둘로 나누어 이분 그래프로 생각하는 것은 자주 등장하는 아이디어이다. 특히, 추가 점수를 주는 각 X 모양을 구성하는 칸들이 이 이분그래프의 한 쪽에 모여있다는 점을 잘 활용해보자.&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://www.acmicpc.net/problem/30935&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ30935] Task Assignment to Two Employees&lt;/a&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;&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://www.acmicpc.net/problem/3611&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ3611] 팀의 난이도&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;\(G\)의 부분그래프에 대하여 \(\frac{\lvert E\rvert}{\lvert V\rvert}\) 의 최댓값을 구하는 것은 \(\lvert E\rvert - \lambda\lvert V\rvert\)의 최댓값이 0이 되게 하는 \(\lambda\)의 값을 찾는 것과 같다. (참고: &lt;a href=&quot;https://measurezero.tistory.com/2371&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공부기록05&lt;/a&gt;)&lt;br /&gt;고정된 \(\lambda\)에 대하여 위 식을 최대화하는 그래프를 찾는 것은 각 간선을 고르면 점수를 1씩 얻고 정점을 고르면 점수를 \(\lambda\)씩 잃으며 각 간선을 골랐을 때 그 간선을 구성하는 양 끝의 두 정점은 항상 골라야하는 관계가 있을 때 정점과 간선을 적절히 골라 얻을 수 있는 점수를 최대화하는 그래프를 찾는 것과 같다. 이 문제는 주어진 그래프의 간선과 정점을 각각 새로운 정점으로 하고 간선과 정점의 관계를 새로운 간선으로 하는 Project Selection Problem으로 모델링할 수 있다.&lt;br /&gt;주어지는&amp;nbsp;그래프에&amp;nbsp;간선이&amp;nbsp;없을&amp;nbsp;수도&amp;nbsp;있다는&amp;nbsp;점에&amp;nbsp;유의하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고 자료&lt;/h3&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://en.wikipedia.org/wiki/Max-flow_min-cut_theorem&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://en.wikipedia.org/wiki/Max-flow_min-cut_theorem&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Closure_problem&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://en.wikipedia.org/wiki/Closure_problem&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://codeforces.com/blog/entry/101354&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://codeforces.com/blog/entry/101354&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kanpurin.hatenablog.com/entry/moyasu-umeru&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://kanpurin.hatenablog.com/entry/moyasu-umeru&lt;/a&gt;&lt;/p&gt;</description>
      <category>CP자료모음</category>
      <category>Closure Problem</category>
      <category>Max-Flow Min-Cut Theorem</category>
      <category>maximum flow</category>
      <category>Maximum Flow Minimum Cut Theorem</category>
      <category>Maximum Weighted Independent Set</category>
      <category>mincut</category>
      <category>Project Selection Problem</category>
      <category>燃やす埋める</category>
      <category>燃やす埋める問題</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2373</guid>
      <comments>https://measurezero.tistory.com/2373#entry2373comment</comments>
      <pubDate>Sat, 7 Feb 2026 10:00:28 +0900</pubDate>
    </item>
    <item>
      <title>[PS/CP 공부기록] 05. 0-1 Fractional Programming, Dinkelbach's Algorithm</title>
      <link>https://measurezero.tistory.com/2371</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;※ 이 공부기록은 부정확한 내용이 다수 포함되어있을 수 있다. 또한 이 글은 정보 전달이 주 목적이 아닌 개인적인 기록용이어서 체계적으로 내용이 정리되어 있지 않을 수 있다. 읽는이의 양해를 바란다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;들어가기 전에&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저번 공부기록에 이어서 이번에도 Semi-Game Cup 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;글쓴이는 대회중에 A부터 E까지 해결한 다음 이어서 F번 문제를 읽었다. 그리고 문제에 등장한 수식 \(\frac{\sum S_i}{\sum D_i}\)을 보자마자 가져갔던 레퍼런스 문서에 적어둔 Dinkelbach's Algorithm의 형태가 떠올랐고, 이를 활용하면 문제를 해결할 수 있지 않을까 하는 생각이 들었다.&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;그러나 글쓴이는 Dinkelbach's Algorithm은 물론 이러한 분수 형태의 값을 최적화하는 유형의 문제를 한 번도 풀어 본 적이 없었고, 문서에 적어간 내용을 활용할 충분한 배경지식이 있지도 않았다. 단순한 활용 문제가 나왔을 때 사용하면 좋겠다는 정도로만 생각하고 레퍼런스 문서 채우기용으로 대충 넣기만 했었기 때문이다. 그렇게 글쓴이는 문제 해결에 유효한 접근은 하지 못한 채로 대회를 마쳤다.&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;h3 data-ke-size=&quot;size23&quot;&gt;개요&lt;/h3&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;letter-spacing: 0px;&quot;&gt;PS/CP 문제를 해결하다 보면 &lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;\(\frac{\sum A_i}{\sum B_i}\)의 꼴로 나타나는 값의 최적화를 요구하는 문제를 종종 만나볼 수 있다. 이런 형태의 문제는 어떻게 해결할 수 있을까?&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;0-1 Fractional Programming&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0-1 Fractional Programming(0-1 분수 계획법)은 두 길이 \(n\)인 수열 \(a_i\)와 \(b_i\)가 주어졌을 때, feasible set \(\displaystyle \Omega\subseteq\{0,1\}^n\)의 원소 \(x=(x_1,x_2,\cdots,x_n)\)에 대한 \(\frac{\sum_{i=1}^n a_i x_i}{\sum_{i=1}^n b_i x_i}\)의 값의 최댓값 또는 최솟값을 구하는 문제를 말한다. \(\{0,1\}^n\)은 유한집합이므로, 주어진 값의 최댓값 또는 최솟값은 항상 존재함을 확인하자. 이 글에서는 최댓값을 구하는 방법을 위주로 설명할 것이다. 최솟값을 구하는 방법도 뒤의 설명과 크게 다르지는 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 \(\{0,1\}^n\) 꼴의 원소에서 0은 해당 인덱스를 선택하지 않음을 의미하고, 1은 해당 인덱스를 선택함을 의미한다.&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;이 글에서는 각 \(x\in\Omega\)에 대하여 \(\sum_{i=1}^n b_i x_i&amp;gt;0\)을 가정한다. 이 조건은 \(n\)개의 수를 모두 0을 골라 분모를 0으로 만드는 것을 막는다. 대부분의 문제 상황에서 위의 식으로 표현되는 값은 평균, 가중평균, 비율(가치/비용, 가치/무게 등)등이므로 이러한 가정을 하여도 문제될 일이 거의 없다.&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;문제 이해를 돕기 위해, 0-1 Fractional Programming으로 변환할 수 있는 간단한 두 문제를 소개한다. 이 두 문제는 0-1 Fractional Programming을 몰라도 해결할 수 있다.&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://www.acmicpc.net/problem/33849&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ33849] 정말 간단한 문제&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;\(\Omega\)를 크기가 1 이상인 모든 연속부분배열에 대응시키면 0-1 Fractional Programming 문제가 된다.&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://www.acmicpc.net/problem/14922&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ14922] 부분평균&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;\(\Omega\)를 크기가 2 이상인 모든 연속부분배열에 대응시키면 0-1 Fractional Programming 문제가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Solution of 0-1 Fractional Programming&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제의 잘 알려진 해법은 식을 적절하게 변형하여 이분 탐색을 활용한 parametric search를 이용하는 것이다.&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;\(x\in\Omega\)에 대하여 \(\frac{\sum_{i=1}^n a_i x_i}{\sum_{i=1}^n b_i x_i}\)의 최댓값을 \(\lambda^*\)라 하고, 이 값을 갖게 하는 \(x\)(중 하나)를 \(x^*\)라 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 정의에 따라 \(\frac{\sum_{i=1}^n a_i x_i}{\sum_{i=1}^n b_i x_i}\le\lambda^*\)가 성립하며, 등호를 만족시키는 \(x\)가 존재한다.&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;이 때, \(x\in\Omega\)에 대하여 \(\sum_{i=1}^n a_i x_i - \lambda^*\cdot\sum_{i=1}^n b_ix_i \le 0\)도 항상 성립하게 됨을 관찰하자. 이에 대한 보충설명을 하면, 등호조건은 \(x=x^*\)일 때 만족하고, 좌변의 식의 값이 0보다 크게 하는 \(x\)가 존재한다면 그 때의 \(\frac{\sum_{i=1}^n a_i x_i}{\sum_{i=1}^n b_i x_i}\) 값이 \(\lambda^*\)보다 커져 모순이 발생한다는 점을 잘 생각해보자.&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;\(\sum_{i=1}^n b_i x_i\)이 양수이므로, \(\sum_{i=1}^n a_i x_i - \lambda\cdot\sum_{i=1}^n b_ix_i\)는 \(\lambda&amp;lt;\lambda^*\)이면 최댓값이 음수, \(\lambda=\lambda^*\)이면 최댓값이 0, \(\lambda&amp;gt;\lambda^*\)이면 최댓값이 양수가 됨을 확인하자. 따라서, \(\lambda\)값을 하나 정했을 때 \(\sum_{i=1}^n a_i x_i - \lambda\cdot\sum_{i=1}^n b_ix_i\)의 최댓값을 계산할 수 있다면 \(\lambda^*\)가 존재하게 될 초기구간을 먼저 정한 다음 그 범위에서 이분 탐색을 활용한 parametric search를 하는 것으로 \(\lambda\)의 값을 계산해낼 수 있다.&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;이 과정에서, 일반적으로 식 \(\sum_{i=1}^n a_i x_i - \lambda\cdot\sum_{i=1}^n b_ix_i\)은 \(\sum_{i=1}^n x_i(a_i - \lambda\cdot b_i)\)와 같이 정리하여 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Dinkelbach's Algorithm&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 방법만으로도 이 유형의 문제 상당수를 해결할 수 있다. 그러나 이 방법만으로는 몇몇 문제에서 어려움을 겪을 수도 있다. \(\lambda^*\)의 값은 유리수로 나올 수도 있으므로 여러 중간 과정에서 부동소수점 오차를 신경써줘야 하기 때문이다. 0-1 Fractional Programming의 문제 해결과정에서 이러한 부동소수점 연산을 없애버릴 수는 없을까?&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;Dinkelbach's Algorithm은 0-1 Fractional Programming의 최적해 \(x\in\Omega\)를 추가적인 부동소수점 연산 없이 찾는 한 가지 해결 방법이다. 이 알고리즘을 활용하면 \(\lambda^*\)의 값을 부동소수점 연산 없이 유리수의 형태로 구할 수 있다. 앞서 소개한 방법은 부동소수점을 활용하고 충분한 정확도를 확보하기 위해 많은 루프를 돌아야 하므로 일반적으로 Dinkelbach's Algorithm을 활용한 방법보다는 느리다.&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;다음은 Dinkelbach's Algorithm의 과정이다.&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) 먼저 적당한 초기값 \(\lambda\)를 정한다. 조건을 만족하면서 계산하기 편한 \(x\in\Omega\)중 하나를 골라 \(\frac{\sum_{i=1}^n a_i x_i}{\sum_{i=1}^n b_i x_i}\)의 값을 하나 계산하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2) \(\sum_{i=1}^n x_i(a_i - \lambda\cdot b_i)\)가 최댓값을 갖게 하는 \(x\)를 구한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(3) 이 \(x\)에 대하여 \(\sum_{i=1}^n x_i(a_i - \lambda\cdot b_i)\) 의 값이 0이 아니라면 \(x\)에 대한 \(\frac{\sum_{i=1}^n a_i x_i}{\sum_{i=1}^n b_i x_i}\) 의 값을 새로운 \(\lambda\)값으로 하여 (2)로 돌아간다. 0이라면 \(x\)에 대한 \(\frac{\sum_{i=1}^n a_i x_i}{\sum_{i=1}^n b_i x_i}\) 의 값을 최적해로 결정하고 알고리즘을 종료한다.&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;앞서 소개한 이분 탐색을 활용한 parametric search와 풀이 방법은 비슷하지만, Dinkelbach's Algorithm은 답을 찾아가는 과정에서 이분 탐색을 사용하지 않고 계산한 결과를 다시 활용한다. 이러한 과정은 수치해석에서의 Newton-Raphson Method의 과정과 유사하다고 볼 수 있다.&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;0-1 Fractional Programming에서의 Dinkelbach's Algorithm은 최악의 경우 \(O(\lg n+\lg A + \lg B)\)회 루프를 돌면 종료된다는 것을 증명할 수 있다. 여기에서 \(A\)는 \(a_i\)의 최댓값, \(B\)는 \(b_i\)의 최댓값을 의미한다. 위의 루프 횟수를 보면 증명 방법이 감이 올테니 구체적인 증명은 생략한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;여담&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dinkelbach's algorithm보다는 이분 탐색을 사용한 parametric search의 구현이 더 쉽고 웬만한 문제는 다 해결할 수 있으니 Dinkelbach's algorithm은 이해를 못 하더라도 이분 탐색을 사용한 parametric search 풀이만큼은 이해하고 넘어가는 것을 추천한다.&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;0-1 Fractional Programming 문제의 최선의 해결 방법이 항상 이분 탐색을 활용한 parametric search나 Dinkelbach's Algorithm인 것은 아니다. 예를 들어, 방향그래프에서 minimum mean weight cycle을 탐색하는 문제는 Karp's Algorithm(Karp's Minimum Mean Cycle Algorithm)을 이용해 본문의 방법보다 더 빠르게 해결할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;관련 문제&lt;/h3&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://www.acmicpc.net/problem/7686&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ7686] Dropping Tests&lt;/a&gt;, &lt;a href=&quot;https://www.acmicpc.net/problem/27654&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ27654] 시험&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0-1 Fractional Programming 기본 문제이다.&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://www.acmicpc.net/problem/3639&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ3639] K Best&lt;/a&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;a href=&quot;https://www.acmicpc.net/problem/10742&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ10742]&amp;nbsp;PROSJEK&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;길이가 \(K\) 이상인 연속부분배열에 대한 0-1 Fractional Programming 문제로 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kadane's Algorithm과 0-1 Fractional Programming을 같이 활용해보자. &lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/8925&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ8925]&amp;nbsp;GC-비율&lt;/a&gt; &lt;br /&gt;위의 문제와 유사하지만, 추가적으로 그 평균의 최댓값을 가지는 구간을 구해야한다. &lt;br /&gt;부동소수점&amp;nbsp;자료형을&amp;nbsp;활용하면&amp;nbsp;구간을&amp;nbsp;선택하는&amp;nbsp;tie-breaking&amp;nbsp;조건들을&amp;nbsp;확인할&amp;nbsp;때&amp;nbsp;문제가&amp;nbsp;일어나기&amp;nbsp;매우&amp;nbsp;쉽다. &lt;br /&gt;유리수의 형태로 \(\lambda\)의 값을 표현하고, 이진탐색이 아닌 Dinkelbach Algorithm을 이용하면 부동소수점 오차 걱정 없이 정수 자료형만으로 문제를 해결할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/10019&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ10019] Sabotage&lt;/a&gt;&lt;br /&gt;(양&amp;nbsp;끝이&amp;nbsp;포함되지&amp;nbsp;않은)&amp;nbsp;연속부분배열을&amp;nbsp;제거한&amp;nbsp;배열에&amp;nbsp;대한&amp;nbsp;0-1&amp;nbsp;Fractional&amp;nbsp;Programming&amp;nbsp;문제로&amp;nbsp;볼&amp;nbsp;수&amp;nbsp;있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/15759&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ15759] Talent Show &lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;무게의 합이 \(W\) 이상인 부분집합에 대한 0-1 Fractional Programming 문제로 볼 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;knapsack DP와 0-1 Fractional Programming을 같이 활용해보자.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/35188&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ35188] 고인물의 마지막 리듬게임&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;글쓴이가 0-1 Fractional Programming을 공부하게 된 계기가 된 문제이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;문제를 해결한 지금 시점에서 돌아보면, 문제 자체를 잘못 이해했어서 이 개념을 제대로 이해했더라도 대회 시간중에 문제를 해결하지 못했을 것이다...&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;추가점수는 1점 미만이지만 정한 구간에서 처리하지 못한 노트가 있다면 2점 이상의 감점이 발생하므로, 구간을 정한다면 해당 구간의 모든 노트를 처리해야 한다. 이 관찰을 이용하여 문제를 해결해보자.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/17986&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ17986] Great GDP&lt;/a&gt;&lt;br /&gt;1번 정점을 포함하는 subtree에 대한 0-1 Fractional Programming 문제로 볼 수 있다.&lt;br /&gt;tree&amp;nbsp;dp와&amp;nbsp;0-1&amp;nbsp;Fractional&amp;nbsp;Programming을&amp;nbsp;같이&amp;nbsp;활용해보자.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; 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://www.acmicpc.net/problem/6141&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ6141]&amp;nbsp;Sightseeing&amp;nbsp;Cows&lt;/a&gt; &lt;br /&gt;시작점을 자유롭게 고를 수 있고, 둘 이상의 사이클로 분해 가능한 사이클의 경우 그 분해된 사이클 중 적어도 하나는 원래의 사이클 이상의 평균 즐거움을 줄 수 있다는 점을 관찰하자. 이를 이용하면 이 문제는 (둘 이상으로 분해할 수 없는) 모든 부분사이클에 대한 0-1 Fractional Programming 문제로 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;negative cycle detection과 0-1 Fractional Programming을 같이 활용해보자.&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://www.acmicpc.net/problem/15843&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ15843] Traveling Merchant&lt;/a&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;a href=&quot;https://www.acmicpc.net/problem/3611&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ3611] 팀의 난이도&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;\(G\)의 부분그래프에 대하여 \(\frac{\lvert E\rvert}{\lvert V\rvert}\) 의 최댓값을 구하는 것은 \(\lvert E\rvert - \lambda\lvert V\rvert\)의 최댓값이 0이 되게 하는 \(\lambda\)의 값을 찾는 것과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고정된 \(\lambda\)에 대하여 위 식을 최대화하는 그래프를 찾는 것은 각 간선을 고르면 점수를 1씩 얻고 정점을 고르면 점수를 \(\lambda\)씩 잃으며 각 간선을 골랐을 때 그 간선을 구성하는 양 끝의 두 정점은 항상 골라야하는 관계가 있을 때 정점과 간선을 적절히 골라 얻을 수 있는 점수를 최대화하는 그래프를 찾는 것과 같다. 이 문제는 주어진 그래프의 간선과 정점을 각각 새로운 정점으로 하고 간선과 정점의 관계를 새로운 간선으로 하는 Project Selection Problem으로 모델링할 수 있다. (참고: &lt;a href=&quot;https://measurezero.tistory.com/2373&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공부기록06&lt;/a&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;h3 data-ke-size=&quot;size23&quot;&gt;참고 자료&lt;/h3&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://www.keisu.t.u-tokyo.ac.jp/data/1992/METR92-14.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.keisu.t.u-tokyo.ac.jp/data/1992/METR92-14.pdf&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://codeforces.com/blog/entry/146040&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://codeforces.com/blog/entry/146040&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Linear-fractional_programming&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://en.wikipedia.org/wiki/Linear-fractional_programming&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Parametric_search&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://en.wikipedia.org/wiki/Parametric_search&lt;/a&gt;&lt;/p&gt;</description>
      <category>CP자료모음</category>
      <category>0-1 Fractional Programming</category>
      <category>binary search</category>
      <category>Dinkelbach's Algorithm</category>
      <category>parametric search</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2371</guid>
      <comments>https://measurezero.tistory.com/2371#entry2371comment</comments>
      <pubDate>Tue, 3 Feb 2026 10:00:53 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 22516 // C++] Magical Girl Sayaka-chan</title>
      <link>https://measurezero.tistory.com/2372</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;※ 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼 문제는 백준 22516번 문제인 Magical Girl Sayaka-chan이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/22516&quot;&gt;https://www.acmicpc.net/problem/22516&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;\(K\)의 나열에서 증가했다가 감소했다가 다시 증가하는 부분이 있다면 이 부분을 단조증가 또는 단조감소의 형태로 순서를 바꿔 항상 이 부분의 반발력을 유지 또는 줄일 수 있음을 관찰하자. 이러한 관찰을 이용하면 \(K\)의 나열은 증가와 감소의 변화를 최소화하는 방식으로 나열된 경우에서 반발력이 최소가 되는 경우 중 하나를 항상 찾을 수 있음을 알 수 있다.&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;증가와 감소의 변화가 최소화되는 나열방식은 가장 작은 \(K\)에서 가장 큰 \(K\)까지 단조증가하면서 이동한 뒤 이 과정에서 사용하지 않은 모든 \(K\)를 단조감소하게 나열하는 것이다. 이는 모든 \(K\)값을 다 사용하면서 가장 작은 \(K\)에서 가장 큰 \(K\)까지 도달하는 두 개의 수열을 구성하는 것으로도 생각할 수 있으며, 여기까지 생각을 해냈다면 남은 문제는 경찰차 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;아래는 제출한 소스코드이다.&lt;/p&gt;
&lt;pre id=&quot;code_1769651194621&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;algorithm&amp;gt;
#include &amp;lt;cstring&amp;gt;
using namespace std;
typedef long long ll;

ll ans = 0x3f3f3f3f3f3f3f3f;
int N, M, L;
int A[2001]; ll P[100001];
ll dp[2001][2001];

ll func(ll i, ll j) {
    if (i &amp;lt; j) swap(i, j);
    if (dp[i][j] &amp;lt; 0x3f3f3f3f3f3f3f3f) return dp[i][j];
    ll &amp;amp;ret = dp[i][j];
    if (i == j + 1) {
        for (int k = 0; k &amp;lt; j; k++) ret = min(ret, func(j, k) + (P[A[i]] - P[A[k] - 1]) / L);
    }
    else ret = func(i - 1, j) + (P[A[i]] - P[A[i - 1] - 1]) / L;
    return ret;
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; N &amp;gt;&amp;gt; M &amp;gt;&amp;gt; L;
    for (int i = 0; i &amp;lt; N; i++) cin &amp;gt;&amp;gt; A[i];
    sort(A, A + N);
    for (int i = 1; i &amp;lt;= M; i++) cin &amp;gt;&amp;gt; P[i], P[i] += P[i - 1];

    memset(dp, 0x3f, sizeof(dp));
    dp[0][0] = 0;
    dp[1][0] = (P[A[1]] - P[A[0] - 1]) / L;
    for (int k = 0; k + 1 &amp;lt; N; k++) ans = min(ans, func(N - 1, k) + (P[A[N - 1]] - P[A[k] - 1]) / L);
    cout &amp;lt;&amp;lt; ans;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 22516</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2372</guid>
      <comments>https://measurezero.tistory.com/2372#entry2372comment</comments>
      <pubDate>Fri, 30 Jan 2026 10:00:07 +0900</pubDate>
    </item>
    <item>
      <title>[PS/CP 공부기록] 04. Green Hackenbush</title>
      <link>https://measurezero.tistory.com/2370</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;※ 이 공부기록은 부정확한 내용이 다수 포함되어있을 수 있다. 또한 이 글은 정보 전달이 주 목적이 아닌 개인적인 기록용이어서 체계적으로 내용이 정리되어 있지 않을 수 있다. 읽는이의 양해를 바란다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;들어가기 전에&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공부기록03까지 쓰고 나서 글쓴이는 Semi-Game Cup 4 오프라인 대회를 준비하고 참가했다. 지난 대회와 마찬가지로 이번 대회에서도 B와 E에서 구현 과정에 문제가 있었고 많은 WA를 받았지만, 그래도 결국 문제 해결에는 성공해 5문제를 풀어낼 수 있었다. 특히 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;대회 이름이 이름이다보니 이번 대회를 대비하면서 여러 게임이론과 관련된 주제를 공부했는데, 그 중 Green Hackenbush의 해법은 흥미로운 내용이라고 생각했고, 우리말로 된 좋은 증명 자료를 찾기 어려워 관련 공부 기록을 남겨두기로 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Hackenbush&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hackenbush는 Conway가 고안한 게임 중 하나로, blue edge, red edge, green edge의 세 가지 종류의 간선으로 구성된 그래프 위에서 Left와 Right의 두 플레이어가 진행하는 게임이다. 이 그래프의 몇몇 정점은 ground 정점으로 지정되어 있으며, 모든 정점에서는 적어도 하나의 ground 정점과 이어지는 경로(간선의 색은 신경쓰지 않는다.)를 찾을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제, Left와 Right는 차례를 번갈아가며 게임을 진행한다. 자신의 차례에 Left는 blue edge 또는 green edge 중 정확히 하나의 간선을 골라 제거해야 하며, Right는 red edge 또는 green edge 중 정확히 하나의 간선을 골라 제거해야 한다. 플레이어가 간선을 제거하고 나면, 어떠한 ground 정점과도 이어지는 경로가 없는 모든 정점 및 그 정점을 포함하는 모든 간선을 지운다. 자신의 차례에 지울 수 있는 간선이 없는 플레이어가 패배한다.&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;Hackenbush를 구성하는 그래프 \(G\)가 유한하다면, 매 차례가 지날 때마다 \(G\)의 간선의 개수는 줄어들 뿐 늘어나거나 유지될 수는 없으므로 게임은 항상 끝나며 승패가 결정됨을 확인하자. 이 아래로 Hackenbush를 이루는 그래프는 유한하다고 가정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Green Hackenbush&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Green Hackenbush는 blue edge와 red edge 없이 green edge만으로 구성된 Hackenbush를 의미한다. Green Hackenbush의 가장 큰 특징은 Left와 Right가 할 수 있는 행동의 집합이 서로 같은 impartial game이라는 것이다. 일반 Hackenbush에서는 blue edge와 green edge가 존재하므로 impartial game이 될 수 없다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 아래로 blue edge와 red edge를 언급할 일이 특별히 없으므로, 이제부터 색에 대한 특별한 언급이 없는 간선은 모두 green edge로 간주하자.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이제 Green Hackenbush는 최선을 다할 시 선공과 후공 중 누가 이길지 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Green Hackenbush Forest&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 ground 정점와 이어진 간선은 많아야 하나이며 다른 모든 정점과 이어진 간선은 많아야 두 개인 단순한 형태를 생각해보자. 이 형태의 경우 주어진 green hackenbush는 각 ground 정점과 이어진 각 maximal subgraph의 간선의 개수를 token의 개수로 한 nim game으로 치환할 수 있다. 따라서 이 형태의 green hackenbush는 nim game과 같은 방법으로 선공과 후공의 승패를 판단할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 형태에서 ground 정점과 이어진 간선의 개수 제약은 지우더라도 ground 정점을 split하는 것으로 어렵지 않게 nim game으로 다시 모델링할 수 있다는 점을 확인하자. 물론 반대로 다시 ground 정점을 합쳐도 전체 게임의 grundy number는 변하지 않는다.&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;이제 ground 정점이 하나이고 이를 root로 하는 트리 구조의 green hackenbush를 해결해보자. 이 경우는 다음과 같은 lemma를 활용하면 어렵지 않게 해결할 수 있다.&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;i&gt;&lt;b&gt;lemma&lt;/b&gt;&lt;/i&gt;: ground 정점이 하나이고 이를 root로 하는 트리 구조의 green hackenbush graph를 \(G\)라 하고, 이 그래프의 grundy number를 \(n\)이라 하자. 이 때, 정점 하나와 이 정점을 기존 ground 정점과 잇는 간선 \(e\)을 추가하고, 새로 추가한 정점을 ground 정점으로 하며 기존 ground 정점을 일반 정점으로 하는 새로운 그래프 \(G'\)의 grundy number는 \(n+1\)이다.&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;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;강한 수학적 귀납법(strong induction)을 이용한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;\(G\)의 정점의 개수가 \(k\) 미만일 때 위 lemma가 성립한다고 가정하자. 이 가정 하에서 \(G\)의 정점의 개수가 \(k\)일 때 위 lemma가 성립함을 보이면 증명이 끝난다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;\(G\)의 정점의 개수를 \(k\)개라고 하고, grundy number를 \(n\)이라 하자. 그리고 위 lemma와 같이 수정한 그래프를 \(G'\)라 하자.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 때, \(G'\)에서 간선 \(e\)를 제거하면 ground 정점을 제외한 \(G'\)의 모든 정점과 간선이 지워지므로 다음 상태는 grundy number가 0이 된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 \(G\)에서 \(G\)를 구성하는 간선을 하나 지우면 \(G\)의 정점의 개수는 \(k\)보다 작아지므로 (\(G\)는 트리이기 때문) 위 lemma를 적용할 수 있다는 점을 활용하면, \(G'\)에서 \(e\)가 아닌 간선을 지웠을 때 얻을 수 있는 다음 상태의 grundy number에는 1부터 \(n\)까지는 확실하게 있으며, \(n+1\)은 확실하게 없음을 알 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;모든 간선을 제거하는 경우를 고려했을 때, 다음 상태로 가능한 grundy number에는 0부터 \(n\)까지는 확실하게 있으며, \(n+1\)은 확실하게 없음을 알 수 있다. 따라서 \(G'\)의 grundy number는 \(n+1\)이다. □&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 lemma를 활용하면 ground 정점이 하나인 tree 형태의 그래프의 형태로 주어지는 green hackenbush를 tree DP와 같이 해결할 수 있다. 또한, 모든 maximal subtree에 ground 정점이 정확히 하나씩만 있는 green hackenbush forest 또한 각 maximal subtree의 grundy number를 이용하여 해결할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Fusion Principle&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 forest 형태가 아닌 \(G\)에 대하여 green hackenbush의 grundy number의 값을 구해보자. 이는 fusion principle이라고 불리는 다음 정리를 활용하는 것으로 얻을 수 있다.&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;i&gt;&lt;b&gt;fusion principle&lt;/b&gt;&lt;/i&gt;: \(G\)의 임의의 cycle에 대하여, 이 cycle에 속한 모든 정점을 하나의 정점으로 합쳐 얻은 새로운 그래프 \(G'\)의 grundy number는 \(G\)의 grundy number와 같다.&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;여기서 cycle의 모든 정점을 합친다는 것은 사이클에 포함된 정점의 집합의 크기가 1이 될 때까지 이 집합에서 두 정점을 골라 다음과 같은 fusion 연산을 하고 새로 생성한 정점을 집합에 추가하는 일을 반복하는 것을 말한다.&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;i&gt;&lt;b&gt;fusion&lt;/b&gt;&lt;/i&gt;: 두 정점 \(x\)와 \(y\)의 fusion은 새로운 정점 \(xy\)를 만들어 \(x\)와 \(y\)를 끝점으로 갖는 모든 간선의 대응되는 끝 점을 \(xy\)로 변경한 뒤 \(x\)와 \(y\)를 지운다. 이 과정에서 \(x\)와 \(y\)를 잇는 간선, \(x\)의 self-loop, \(y\)의 self-loop는 각각 \(xy\)의 self-loop가 된다.&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;다음은 fusion principle의 증명이다. 명제는 단순하지만 증명이 상당히 기므로, 대략적인 흐름을 먼저 소개한다.&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;(0) green hackenbush에 대한 다른 principle들을 먼저 소개한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(1) 귀류법을 사용하여 cycle을 fusion할 시 grundy number에 변화가 생기는 그래프의 존재를 가정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2) 그 중 (조건을 만족하는) 가장 작은 그래프가 unicyclic한 형태가 되어야 함을 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(3) 그러나 이 (조건을 만족하는) 가장 작은 unicyclic한 그래프는 (유일한) cycle을 fusion해도 grundy number가 변하지 않음을 보여 (1)의 가정이 틀림을 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(4) (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;아래는 fusion principle의 증명에 활용하는 두 principle인 colon principle과 parity principle의 소개와 증명이다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;colon principle&lt;/b&gt;&lt;/i&gt;: ground 정점이 유일한 그래프 \(G\)와 ground가 아닌 한 정점 \(v\)를 생각하자. 이 때, 두 connected subgraph \(G'\)와 \(H\)가 (1) 공통 정점은 \(v\) 뿐이며 (2) 공통 간선은 존재하지 않고 (3) 두 그래프의 정점 집합의 합집합은 \(G\)의 정점의 집합과 같고 (4) 두 그래프의 간선 집합의 합집합은 \(G\)의 간선의 집합과 같다다는 성질을 가지고 있다고 가정하자. 이 때, \(G\)에서 \(v\)를 제외한 \(H\)의 구성요소를 제거하고, 대신 원래 \(H\)에서 \(v\)를 ground취급하였을 때의 \(H\)의 grundy number와 같은 grundy number를 갖는 다른 그래프 \(H'\)의 ground를 \(v\)에 붙여도 \(G\)의 grundy number는 변하지 않는다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;증명: 기존의 그래프 \(G\)에서 위의 과정을 따라 새로 만든 그래프를 \(K\)라 하자. \(G\)와 \(K\)를 같이 놓고 동시에 게임을 진행할 때 후공이 이긴다는 것을 보이면 두 게임의 grundy number가 같다는 것을 증명할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;선공은 (1) \(G\) 또는 \(K\)에서 \(G'\)에 속한 간선과 대응되는 간선을 지우거나, (2) \(H\) 또는 \(H\')에 속한 간선과 대응되는 간선을 지우는 두 가지 행동 중 하나를 해야 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(1) 선공이 \(G'\)에 속한 간선과 대응되는 간선을 제거한다면 후공은 다른 게임의 대응되는 \(G'\)간선을 따라서 제거할 수 있다. 이 과정에서 선공이 \(H\) 또는 \(H'\)을 ground와 연결되지 않게 만들었다면 대응되는 후공의 행동도 \(H'\) 또는 \(H\)를 ground와 연결되지 않게 만든다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(2) 선공이 \(H\) 또는 \(H'\)에 속한 간선과 대응되는 간선을 제거하면, 후공은 \(H\)와 \(H'\)의 grundy number가 기존에 같았으므로 다시 \(H\)와 \(H'\)의 grundy number가 같아지게 하는 행동을 항상 할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 선공이 어떤 행동을 하더라도 후공은 대응되는 행동을 할 수 있다. 즉, 후공이 항상 승리한다□&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;parity principle&lt;/b&gt;&lt;/i&gt;: green hackenbush forest의 grundy number의 parity(2로 나눈 나머지)는 green hackenbush forest를 구성하는 간선의 개수의 parity와 같다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;증명: green hackenbush forest의 grundy number 계산 과정은 두 grundy number의 xor과 앞선 lemma를 이용하여 간선을 따라 이동하며 grundy number를 1 증가시키는 것으로 이루어져 있다. 이 두 과정은 모두 parity를 보존한다.□&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 fusion principle의 증명이다.&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&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;fusion principle이 거짓이라고 가정하자. 그렇다면 어떤 cycle이 있어서 그 cycle을 fusion했을 때 grundy number에 변화가 있는 그래프가 존재한다. 그러한 그래프 중 가장 간선의 개수가 적은 그래프, 그러한 그래프도 여럿이라면 그 중 가장 정점의 개수가 가장 적은 그래프 중 하나를 골라 \(G\)라고 하자.&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;이 때, \(G\)의 특징을 다음과 같이 좁혀나갈 수 있다.&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) \(G\)의 ground 정점은 유일하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지 않다면 두 ground 정점을 fusion하여 정점이 더 적으면서 위의 성질을 만족하는 그래프를 항상 얻을 수 있기 때문이다.&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;(2) \(G\)의 임의의 cycle 위의 두 정점을 fusion하면 grundy number가 달라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지 않다면 그 두 정점을 fusion하여 정점이 더 적으면서 위의 성질을 만족하는 그래프를 얻을 수 있기 때문이다.&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) \(G\)의 임의의 두 정점 \(x\)와 \(y\)에 대하여 \(x\)와 \(y\)를 잇는 세 개의 edge-disjoint path는 존재할 수 없다. 즉, \(G\)는 cactus graph이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;증명: \(G\)의 어떤 두 정점 \(x\)와 \(y\) 사이에 세 개의 edge-disjoint path가 존재한다고 가정하자. 그리고 \(G\)에서 \(x\)와 \(y\)를 fusion한 그래프를 \(H\)라 하자. (2)에 의해 \(G\)와 \(H\)의 grundy number는 달라야한다. 따라서, 두 green hackenbush \(G\)와 \(H\)를 같이 놓고 플레이하면 선공이 이겨야한다. 그러나 이 게임은 후공이 이기는 게임이다. 선공이 어떤 간선을 고르더라도 후공이 \(G\)와 \(H\) 사이의 대응되는 에지를 고르는 것으로 항상 응수할 수 있기 때문이다.&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) \(G\)의 모든 사이클은 ground 정점을 지난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;증명: ground 정점을 지나지 않는 cycle이 있다고 가정하자. 이 때, 이 cycle 위의 정점 중에는 cycle에 속하는 간선을 지나지 않으면서 ground까지 이어질 수 있는 정점이 존재할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;case 1) 그러한 정점이 둘 이상 있다고 가정하자. 그 정점들 중 두 정점을 \(x\)와 \(y\)라 하자. 이 때, \(x\)와 \(y\)에서 각각 ground 정점으로 이어지는 각 경로는 가정에 따라 cycle과 edge-disjoint하며, 두 경로가 (각각 \(x\)와 \(y\)쪽을 기준으로) 처음으로 만나는 정점 \(z\)가 존재한다(늦어도 ground에서는 만나므로). 이제 \(x\)와 \(y\)는 \(z\)를 통하는 cycle과 disjoint한 path 하나와 cycle을 통한 path까지 해서 세 개의 edge-disjoint path를 가지게 된다. 이는 (3)에 모순이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;case 2) 그러한 정점이 유일하다고 가정하자. 그렇다면 이 정점을 기준으로 &lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;ground를 포함하며 앞서 정한 cycle은 포함하지 않는 maximal한 connected component \(X\)와 그 외 모든 정점(앞서 정한 &lt;/span&gt;cycle을 온전히 포함한다)으로 구성된 connected component \(H\)의 두 connected component로 그래프를 나누어 생각할 수 있다. 3에서 언급한 cactus graph의 형태를 생각해보면 이는 자연스러울 것이다. 한편, \(H\)의 간선 개수는 \(G\)보다 적으므로, 가정에 의해 \(H\)에 속한 앞서 정한 cycle을 fusion하여 얻는 그래프 \(H'\)의 grundy number는 \(H\)와 동일하다. 따라서 colon principle에 의해 \(G\)에서 이 cycle은 fusion해도 grundy number가 변하지 않음을 알 수 있다. 이는 가정에 모순이다.&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) \(G\)의 cycle은 유일하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;증명: cycle이 여러 개 존재한다고 가정해보자. 그 중 하나는 fusion하면 grundy number가 변하는 것으로 고르고, 다른 하나는 아무 cycle이나 하나 고를 수 있다. 이 때, (4)에 따라 두 cycle은 (유일한) ground 정점을 공유하고, (3)에 따라 두 cycle은 ground 정점 외에는 공통 정점이 존재하지 않는다. 한편, ground 정점을 통하지 않으면 두 cycle 사이에는 이동할 수 있는 path가 존재하지 않아야 하는데, 이유는 그러한 path가 존재한다면 각 cycle과 각각 한 정점에서만 만나는 path가 존재하고, 그 path의 양 끝점 사이에는 3개의 edge-distinct path가 생겨 (3)에 모순되기 때문이다. 따라서, \(G\)가 나타내는 게임은 서로 다른 ground 정점을 갖는 두 개의 게임처럼 생각할 수 있게 되는데, 각 게임의 간선의 개수는 \(G\)의 간선의 개수보다 적으므로 어떤 cycle을 fusion해도 grundy number는 변하지 않아야 한다. 이는 모순이다.&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;따라서 \(G\)는 정확히 하나의 ground 정점과 이 정점을 포함하는 하나의 cycle을 갖는 그래프, 즉 ground 정점이 사이클에 포함된 unicyclic graph이어야 한다. 이제 \(G\)의 (유일한) cycle을 fusion해도 grundy number가 변하지 않음을 보이면 증명이 끝난다.&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;\(G\)의 (유일한) cycle을 fusion하여 얻는 그래프를 \(H\)라 하자. 이제 두 그래프를 같이 놓고 게임을 진행할 때 후공이 이긴다는 것을 보일 것이다.&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) \(G\)의 cycle에 포함되지 않은 간선(또는 그와 대응되는 \(H\)의 간선) 지우기, (2) \(G\)의 cycle에 포함되는 간선 지우기, (3) \(H\)의 원래 cycle에 포함되어 있던 간선 지우기의 세 가지가 있다.&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)의 경우, 그에 대응되는 다른 그래프의 간선을 지우면 \(G\)의 간선의 개수가 줄어들어 정의에 따라 grundy number의 변화 없이 \(G\)의 cycle을 fusion할 수 있게 되는데, 그렇게 얻은 그래프는 \(H\)와 같으므로 선공이 패배한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2)의 경우, 이제 주어진 게임은 총 홀수개의 간선으로 이루어진 green hackenbush forest가 된다. 따라서 parity principle에 의해 이 green hackenbush forest의 grundy number는 0이 될 수 없으며, 후공이 승리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(3)의 경우, cycle을 이루는 간선의 개수가 짝수개라면 후공도 \(H\)에서 원래 cycle에 포함되어 있던 간선을 따라 지워 (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;따라서, cycle을 이루는 간선이 홀수개이고 선공이 \(H\)에서 원래 cycle에 포함되어 있던 간선을 지웠을 때의 필승법을 찾아 문제를 해결할 수 있다. 그 방법은 다음과 같다.&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;방법을 설명하기 전에 지금의 상황을 다시 한번 정리하면, ground 정점이 cycle에 속해있으며 cycle을 이루는 간선이 홀수개인 unicycle graph \(G\)와 이 그래프의 cycle을 fusion한 그래프 \(H\)를 같이 놓고 게임을 시작한 뒤, 선공이 \(H\)에서 기존 \(G\)의 cycle에 포함되어 있던 간선 하나를 지운 뒤 후공이 필승전략을 찾는 상황이다.&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;(0) 먼저, 주어진 그래프에서 &quot;cycle에 포함된 간선&quot;을 포함하지 않는 각각의 maximal한 subtree들을 같은 grundy number를 갖는 일자형 그래프로 바꾸자. colon principle에 의해 이 과정에서 grundy number의 변화는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(1) \(G\)의 cycle을 구성하는 각 간선에 라벨\(A\)와 라벨\(B\)를 다음 규칙에 따라 붙인다. 단, ground 정점과 이어진 cycle의 두 간선은 인접하지 않은 것으로 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;규칙&amp;gt; 인접한 두 간선의 공통정점에 붙어 있는 일자형 그래프의 길이(=grundy number)가 홀수이면 두 간선은 같은 라벨을 갖고, 짝수이면 두 간선은 다른 라벨을 갖는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2) 일반성을 잃지 않고, 위와 같이 라벨을 붙였을 때 홀수개의 간선에 붙은 라벨을 A, 짝수개의 간선에 붙은 라벨을 B라 하자. 이 때, 라벨 \(B\)가 붙은 간선은 제거시 전체 게임의 grundy number가 \(2 \mod 4\)가 되므로 라벨 \(B\)가 붙은 간선은 후보가 될 수 없음을 알 수 있다. 라벨 \(A\)가 붙은 간선은 제거시 전체 게임의 grundy number가 \(0 \mod 4\)이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(참고) (2)에서 언급한 2번째 비트의 parity 성질의 증명 흐름:&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; grundy number가 \(1 \mod 4\) 또는 \(3 \mod 4\)인 일자형 그래프가, 다른 라벨이 붙은 두 간선 사이의 정점에는 grundy number가 \(0 \mod 4\) 또는 \(2 \mod 4\)인 일자형 그래프가 붙어있다. 이 때, ((1)) 먼저 어떤 사이클의 간선을 끊어도 위의 각 정점의 트리의 grundy 수의 2번째 비트는 전체 게임의 grundy number의 4로 나누었을 때의 2번째 비트에 영향을 주지 않음을 보이고, ((2)) A와 B를 임의로 나열한 문자열에서 인접한 두 문자를 교환하더라도 grundy number의 4로 나눈 나머지에는 변화가 없음을 보이고, ((3)) 따라서 cycle에서 지우는 간선이 \(A\)인지 \(B\)인지 경우를 나누고 해당 간선의 양옆으로 A와 B의 개수의 parity가 어떻게 되는지 경우를 나누어 A...AB...B꼴로 나열시킨 뒤 grundy number가 어떻게 계산되는지를 확인하는 것으로 증명할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(3) 이제 \(G\)에서 라벨 \(B\)가 붙은 각각의 간선에 대하여 해당 간선이 잇는 두 정점을 fusion한다. 그리고 (0)에서 한 것과 같이 &lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;&quot;cycle에 포함된 간선&quot;을 포함하지 않는 각각의 maximal한 subtree들을 같은 grundy number를 갖는 일자형 그래프로 바꾸자. 그리고 각 일자형 그래프의 길이를 절반으로(소수점이 발생할 경우 버림을 시행) 바꾼다. 이 과정을 거쳐 변화한 \(G\)의 간선을 지웠을 때의 grundy number는 기존 \(G\)의 대응되는 간선을 지울 때 얻게 되는 grundy number의 절반이 된다. 이 내용의 증명은 그래프를 직접 그려 몇 번 해보면 어렵지 않으므로 생략한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;(참고) 모든 라벨이 모두 같은 경우는 사용하지 않은 라벨이 짝수개 사용되었다고 생각할 수 있으므로 어떤 간선도 fusion되지 않고 일자형 그래프의 길이만 변화한다.&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;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;(4) 과정 (2)와 과정 (3)을 cycle의 간선이 오직 하나 남을 때까지 반복한다. 이 때, 남은 cycle의 간선 중 하나를 지우는 것이 후공의 필승전략이 됨은 어렵지 않게 알 수 있다.&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;따라서 \(G\)와 \(H\)를 같이 놓고 하는 게임은 선공이 패배한다. □&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;\(G\)에 (self-loop를 제외한) cycle이 없어질 때까지 (self-loop를 제외한) 임의의 cycle을 골라 fusion하는 것을 반복하면 결국 \(G\)의 각 biconnected component가 하나의 노드로 압축됨을 알 수 있다. 또한 각 self-loop는 그냥 그 정점에서 (self-loop마다) 새 정점을 만들어 이은 것과 똑같이 취급할 수 있다. 따라서 fusion principle을 활용하면 일반 green hackenbush를 green hackenbush forest 문제로 환원할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마무리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 green hackenbush의 grundy number는 증명은 몰라도 방법만 알면 biconnected component, tree DP의 개념을 이용하여 구할 수 있다. Semi-Game Cup 4 준비가 아니었다면 green hackenbush도 그냥 넘겼을 것 같지만, 그래도 이를 구현해보는 것은 구현이 약한 글쓴이에게 좋은 연습이 되었다. 구현 연습삼아 green hackenbush 문제를 풀어보는 것은 어떨까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;관련 문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;green hackenbush forest는 관련 내용을 잘 모르더라도 대회장에서 충분히 유도할만 하고, 실제로 대부분의 green hackenbush 관련 문제는 이정도 선에서 그치는 정도로 출제되는 것으로 보인다.&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;일반 그래프에서의 green hackenbush는 풀이가 올바르다는 증명이 비직관적이고 길어서 그런지 출제 사례가 드문 것 같다. 다만, green hackenbush의 풀이 자체는 PS/CP에서 자주 사용되는 내용만을 활용하여 해낼 수 있으므로, 일단 풀이가 한 번 알려지면 종종 출제될 수도 있을 법하다고 생각한다.&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://atcoder.jp/contests/agc017/tasks/agc017_d&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[AGC017D] Game on Tree&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접적으로 Green Hackenbush Tree를 해결할 것을 요구하는 문제이다.&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://www.acmicpc.net/problem/13893&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ13893] Dictionary Game&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주어진 문제는 trie의 형태를 한 Green Hackenbush Forest로 모델링할 수 있다. 새로운 단어를 추가할 때마다 trie의 grundy number를 재계산하면 시간복잡도가 너무 커지므로, 적절히 각 subtree의 grundy number를 저장 및 업데이트해 문제를 해결하자.&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://ipsc.ksp.sk/2003/real/problems/g.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[IPSC2003G] Got Root?&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접적으로 일반 그래프에서의 Green Hackenbush를 해결할 것을 요구하는 문제이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;오래된 문제라서 입력 설명이 불친절한데, 입력으로 들어오는 테스트케이스의 수는 20 이하, 각 테스트케이스에서 주어지는 그래프의 정점과 간선의 개수는 각각 50000 이하로 잡고 문제를 해결해보자. 또한, &lt;/span&gt;서로 다른 두 정점을 잇는 중복된 간선(multiple edges)이 입력으로 들어올 수도 있고, self-loop 또한 입력으로 들어올 수도 있다는 점에 유의하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고 자료&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Winning Ways for Your Mathematical Plays (Elwyn R. Berlekamp, John H. Conway, Richard K. Guy)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;On Numbers and Games (John H. Conway)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Hackenbush&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://en.wikipedia.org/wiki/Hackenbush&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.math.uchicago.edu/~may/VIGRE/VIGRE2006/PAPERS/Bartlett.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.math.uchicago.edu/~may/VIGRE/VIGRE2006/PAPERS/Bartlett.pdf&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://simonrs.com/eulercircle/cgt2024/agastya-hackenbush.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://simonrs.com/eulercircle/cgt2024/agastya-hackenbush.pdf&lt;/a&gt;&lt;/p&gt;</description>
      <category>CP자료모음</category>
      <category>Biconnected Component</category>
      <category>Game Theory</category>
      <category>graph theory</category>
      <category>Green Hackenbush</category>
      <category>Grundy Number</category>
      <category>Hackenbush</category>
      <category>tree dp</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2370</guid>
      <comments>https://measurezero.tistory.com/2370#entry2370comment</comments>
      <pubDate>Wed, 28 Jan 2026 10:00:10 +0900</pubDate>
    </item>
    <item>
      <title>[PS/CP 공부기록] 03. Cartesian Tree, Treap, Implicit Treap</title>
      <link>https://measurezero.tistory.com/2369</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;※ 이 공부기록은 부정확한 내용이 다수 포함되어있을 수 있다. 또한 이 글은 정보 전달이 주 목적이 아닌 개인적인 기록용이어서 체계적으로 내용이 정리되어 있지 않을 수 있다. 읽는이의 양해를 바란다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;들어가기 전에&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;지금도 글쓴이는 구현력이 많이 부족하다고 느끼지만, 과거에는 구현력이 매우 부족했었다. (implicit) treap은 그 당시에 익혀보려고 도전했다가 실패했던 자료구조 중 하나이다. 이 때문에 글쓴이는 treap을 막연히 어려운 주제라고 생각하고 있었고, 익히는 도중에도 treap을 제대로 익힐 수 있을지 많은 걱정을 하였었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이제 과거의 기억에서 벗어나 treap을 다시 마주할 때가 왔다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;개요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번 글에서 살펴볼 주제는 Cartesian Tree, Treap, Implicit Treap이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Cartesian Tree는 treap과 유사한 점이 많은 자료구조이다. 따라서 이 글에서는 treap을 다루기에 앞서 Cartesian Tree를 먼저 소개할 것이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;한편, Cartesian Tree는 &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;그 자체만으로도 좋은 성질을 가진 자료구조이기도 하다. PS/CP에서도 이러한 성질을 활용한 문제를 종종 만나볼 수 있다. 따라서 이 글에서는 PS/CP에서 Cartesian Tree가 어떻게 활용되는지 또한 간단히 살펴볼 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;treap은 Randomized Binary Search Tree라고도 불리는 자료구조이다. treap은 (binary search) tree와 heap의 합성어로, BST의 성질과 heap의 성질을 동시에 가지는 특징을 따 이름지어졌다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;PS/CP의 범주에서 treap은 BBST의 역할을 수행하는 자료구조이다. 그런데 PS/CP에서는 정답 코드를 작성하는 시간을 줄이는 것도 중요하므로, BBST를 활용하려는 목적만으로는 그냥 불러와서 사용하면 되는 std::set과 std::map, pbds 등을 제치고 굳이 treap을 직접 구현해 사용할 필요가 없다&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;. 그러면 treap은 공부하지 않아도 되는 자료구조일까?&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;implicit treap은 key가 추상화된 treap이다. implicit treap은 다양한 쿼리를 처리하기 좋은 자료구조이므로 알아둘 가치가 충분하다. 특히, implicit treap은 많은 경우 implicit splay tree의 기능을 상당수 대체할 수 있다. 그리고 이것이 이번 글의 최종 목표이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;Cartesian Tree&lt;/span&gt;&lt;/h3&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;letter-spacing: 0px;&quot;&gt;&lt;b&gt;Cartesian Tree&lt;/b&gt;는 서로 다른 수로 구성된 수열에 대하여 다음과 같이 정의되는 binary tree이다.&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;(1) Cartesian Tree의 각 노드는 주어진 수열의 각 수에 대응된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2) Cartesian Tree에서 inorder traversal(중위순회)을 하면 원래의 수열을 얻는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(3) Cartesian Tree는 min-heap property를 갖는다. min-heap property란 부모 노드에 대응되는 수는 (존재하는 모든) 자식 노드에 대응되는 수보다 작아야 한다는 성질을 말한다.&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;즉, 가볍게 서술하면 Cartesian Tree는 수열에서 가장 작은 수를 root로 하고 그 위치를 기준으로 좌우로 나눠진 두 배열에 대하여 이와 같은 실행을 반복하여 얻게 되는 트리의 각 root를 자식으로 갖는 트리를 말한다.&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;여기에서 min이라는 것은 (일상적으로 사용하는) 정수의 대소관계만을 뜻하는 것은 아니므로, 대소의 정의를 적절하게 할 수만 있다면 다른 order topology를 대응시켜도 된다. 예를 들어, min-heap property 대신 max-heap property를 적용해도 대소의 정의가 반대로 된 min-heap property를 만족시킨다고 생각할 수 있으므로 정의에 따라 Cartesian Tree라고 볼 수 있다.&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;Cartesian Tree는 서로 다른 수로 구성된 수열에 대하여 정의되어 있지만, PS/CP 문제풀이에서는 대해서도 등장한 순서에 따라 구분자를 넣는 등의 방법으로 다른 수처럼 취급하여 Cartesian Tree를 활용하기도 한다. 다만 이 경우, 문제 풀이 과정에서 같은 수의 취급에 유의해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;\(O(n)\) Construction of Cartesian Tree&lt;/h3&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\)인 배열의 Cartesian Tree는 몇 가지 방법으로 \(O(n)\)에 구성할 수 있다. 이 글에서는 monotonic stack을 이용한 접근을 알아본다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;수열 \(a_1, a_2,\cdots a_n\)의 Cartesian Tree는 &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;1부터 \(n\)까지의 인덱스 \(i\)에 대하여 다음의 1~4의 과정을 차례대로 진행하는 것으로 구성할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(0) 빈 stack을 준비한다. 이 stack은 monotonic stack이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(1) stack이 비거나 끝에 저장된 인덱스의 수열 값이 \(a_i\)보다 작아질 때까지 stack의 원소를 빼낸다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(2) stack이 비지 않았다면 스택의 끝에 저장된 인덱스의 노드의 오른쪽 자식을 \(i\)로 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(3) (1)에서 stack에서 마지막으로 뺀 원소가 존재한다면 그 원소를 \(i\)의 왼쪽 자식을 그 원소로 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(4) stack에 \(i\)를 넣는다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;랜덤 수열에 대한 Cartesian Tree&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;수열의 수가 랜덤으로 주어질 때 Cartesian Tree의 높이의 기댓값은 어느 정도일까?&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그 답은 quicksort와 비교해 생각해보면 금방 얻을 수 있다. 매번 주어진 배열에서 가장 작은 수를 고르고 그 양 옆의 각 두 부분수열에 대하여 재귀적으로 기대 높이를 구하는 과정에서 배열의 길이가 줄어드는 과정은. quicksort에서 배열의 크기가 줄어드는 과정이랑 완전히 대응된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 크기 \(n\)인 랜덤 수열의 Cartesian Tree의 높이의 기댓값은 \(O(\lg n)\)이며, 이 수열의 높이가 \(O(n)\)이 될 확률은 이 수열을 quicksort로 정렬하는 시간복잡도가 \(O(n^2)\)가 될 확률에 가깝다는 것을 알 수 있다. 즉, quicksort의 경우 적절하게 자료의 순서를 먼저 섞어주면 사실상 \(O(n\lg n)\)에 수행할 수 있는 것처럼, 가중치가 임의로 주어진 수열의 Cartesian Tree의 높이는 사실상 \(O(\lg n)\)이 된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 랜덤 수열에 대한 Cartesian Tree는 어느 한 쪽으로 노드가 지나치게 치우치지 않은, 어느 정도 균형 잡힌 이진 트리와 같은 모양을 하게 된다는 점을 알 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Treap(Randomized Binary Search Tree; Randomized BST)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 관찰로부터, key와 랜덤 가중치를 가지는 노드로 구성된, key 순서대로 정렬된 배열의 랜덤 가중치에 대한 Cartesian Tree는 균형 잡힌 이진 트리처럼 다룰 수 있다는 것을 알 수 있다. 이와 같은 구성의 자료구조를 &lt;b&gt;treap&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;treap은 tree와 heap이 합쳐져 만들어진 이름이다. treap 자료구조의 key는 BST처럼 정렬되어 있으며, 각 노드의 배치는 랜덤 가중치에 대한 heap 구조를 이루고 있기 때문이다. 다른 이름으로는 랜덤성을 이용해 만든 BST라는 측면에서 Randomized Binary Search Tree라고 부르기도 한다.&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;treap을 실질적인 BBST처럼 사용하려면 자료의 삽입 및 제거 등 기본 BBST 연산을 수행할 수 있어야 한다. treap에서 이러한 연산은 대부분 split과 merge의 두 가지 기본 연산을 활용해 구현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;split 연산&lt;/h4&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;b&gt;split&lt;/b&gt;은 주어진 treap을 기준이 되는 값 \(val\)을 기준으로 두 개의 treap으로 쪼개는 연산이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설명 편의상 \(val\)이 treap의 key 와 distinct하다고 가정하자. 이 때 split은 \(val\)을 기준으로 이 값보다 작은 key가 저장되어 있는 treap과 큰 값이 저장되어 있는 treap으로 쪼갠다. 이를 진행하면서 treap의 min-heap property가 깨지지 않아야 한다는 점에 유의하자.&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;split하려는 treap의 root와 기준이 되는 값 \(val\)이 주어질 때, split 연산은 (1) root에서부터 시작해서 현재 노드가 어느 쪽 treap에 들어갈지를 기준삼아 leaf까지 내려가는 과정과 (2) 다시 root로 돌아오면서 노드를 적절하게 이어붙여 두 개의 treap으로 split하는 과정으로 이루어져 있다. 이 과정을 자세히 살펴보자.&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)은 단순하다. 현재 노드의 key와 \(val\)을 비교했을 때 key가 \(val\)보다 작다면 현재 노드는 split 이후 왼쪽 treap에 속하게 될 것이다. 한편, 이 노드의 왼쪽 자식 노드를 root로 갖는 부분 treap의 노드의 모든 key는 현재 노드의 key보다 작은 값을 가지므로, 이 노드들 또한 모두 왼쪽 treap에 속하게 된다. 그러나 이 노드의 오른쪽 자식 노드는 어느 쪽 treap에 속하게 될지 정보가 부족하다. 따라서 오른쪽 노드로 탐색을 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;key가 \(val\)보다 큰 경우도 위와 거의 같다. 이 경우 현재 노드는 split 이후 오른쪽 treap에 속하게 되며, 이 노드의 오른쪽 자식 노드와 그 밑에 연결된 노드 오른쪽 treap에 속해야 한다는 점을 알 수 있다. 그리고 왼쪽 자식 노드는 아직 어느 쪽 treap에 붙게 될지에 대한 정보가 부족하다. 따라서 왼쪽 노드로 탐색을 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 탐색은 아무런 정보도 담고 있지 않은 null 노드가 나올 때까지 진행한다.&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;과정 (2)는 정보가 없던 자식 노드쪽의 정보를 재귀적으로 위로 전달하며 왼쪽 treap과 오른쪽 treap을 구성해나가는 과정이다. 정보가 없던 자식 노드에 대하여 왼쪽 treap에 속할 부분treap의 root와 오른쪽 treap에 속할 부분treap의 root를 얻어 현재 노드를 root로 하는 부분treap을 둘로 쪼개어 올라가는 것을 반복하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 treap은 Cartesian Tree의 구조를 따르므로, 어떤 노드가 조상 노드의 왼쪽 자식으로 붙어야 하는 상황이라면 부모로 거슬러 올라가는 과정 중 오른쪽으로 올라가는 상황을 처음 만났을 때 그 노드의 왼쪽 자식으로 붙게 되며, 오른쪽 자식으로 붙어야 하는 상황이라면 부모로 거슬러 올라가는 과정 중 왼쪽으로 올라가는 상황을 처음 만났을 때 그 노드의 오른쪽 자식으로 붙게 됨을 확인하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한편, 이 과정에서 새로 붙게 되는 부모-자식 관계의 노드쌍은 기존 treap에서도 조상-자손 관계에 있었으므로 이와 같은 수정은 min-heap property를 깨지 않는다는 점을 확인하자.&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;과정 (2)까지 마치면 split 과정이 종료되며, 분리된 두 treap과 각각의 root 노드를 얻는다.&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;split 연산의 시간복잡도는 그 과정에서 재귀적으로 들어가는 노드의 깊이에 비례하며, 이 값은 평균적으로 \(O(lg n)\)임을 앞서 살펴보았다. 따라서 각 노드의 가중치를 랜덤으로 부여하면 split의 평균시간복잡도는 \(O(\lg n)\)이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;merge 연산&lt;/h4&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;b&gt;merge&lt;/b&gt;는 주어진 두 개의 treap을 하나의 treap으로 합치는 연산이다. 이 때, 여기에서 주어지는 두 treap의 key의 범위는 서로 겹치지 않아야 한다. 편의상 두 treap을 왼쪽 treap과 오른쪽 treap이라고 부르자.&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;merge 결과로 얻게 되는 treap의 root는 왼쪽 treap의 root거나 오른쪽 treap의 root일 것이다. 그 외의 노드가 root가 된다면 treap의 min-heap property가 깨지기 때문이다.&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;먼저 왼쪽 treap의 root의 가중치가 오른쪽 root의 가중치보다 더 작은 경우를 살펴보자. 이 때, 왼쪽 treap의 root는 최종적으로 merge된 treap의 root가 될 것이다. 또한, 왼쪽 treap의 오른쪽 자식을 root로 갖는 부분 treap과 오른쪽 treap을 merge하여 왼쪽 treap의 왼쪽 자식에 붙이면 key의 정렬 순서도 깨지지 않고 min-heap property도 깨지지 않음을 관찰할 수 있다. 따라서 이를 재귀적으로 수행하는 것으로 merge 연산을 수행할 수 있다.&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;merge 연산의 시간복잡도 또한 split처럼 그 과정에서 재귀적으로 들어가는 노드의 깊이에 비례하며, 이 값은 평균적으로 \(O(\lg n)\)임을 앞서 살펴보았다. 따라서 각 노드의 가중치를 랜덤으로 부여하면 merge의 평균시간복잡도는 \(O(\lg n)\)이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;다른 연산들&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;split과 merge를 이용하면 BBST가 가져야 하는 모든 연산을 어렵지 않게 구현할 수 있다. 예를 들어 특정 key의 삽입은 key를 기준으로 treap을 split한 뒤 삽입하려는 자료 노드 하나로 구성된 tree를 사이에 넣어 merge를 두 번 하면 된다. 또한 특정 key의 제거는 이 key를 기준으로 각각 왼쪽과 오른쪽 자료로 구성된 treap을 split으로 얻어 이들을 다시 merge하면 된다.&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;정렬된 자료가 주어졌을 때, Treap을 \(n\)번의 merge를 통해서도 구성할 수 있지만 Cartesian Tree의 \(O(n)\) 구성과 같은 방식으로 \(O(n)\)에 구성할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Implicit Treap&lt;/h3&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;b&gt;implicit treap&lt;/b&gt;은 treap를 BBST처럼 사용하는 대신 배열에 대응시킨 것과 같이 사용하는 자료구조이다. implicit treap에서 일반 treap의 key에 대응되는 개념은 배열의 인덱스이다. 그러나 implicit treap을 활용하는 과정에서 이러한 인덱스는 쉽게 변경될 수 있고, 실제로 implicit treap은 이러한 변경이 필요할 때 주로 사용하는 자료구조이기도 하다.&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;implicit treap을 활용하는 대표적인 두 예시를 보자.&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) 부분배열을 잘라서 다른 위치에 삽입하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 자르려는 부분배열의 앞뒤와 삽입할 위치를 기준으로 split을 시행한 뒤, 자른 부분배열을 삽입할 위치에 오게끔 적절한 순서로 merge한다.&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;(2) 부분배열의 자료 순서를 뒤집기(역순으로 바꾸기)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 작업을 하려면 먼저 뒤집기 연산에 대한 lazy tag를 노드에 기록해둬야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 뒤집으려는 부분배열의 앞뒤를 기준으로 split을 시행한 뒤, 자른 부분배열 treap의 root에 뒤집기 lazy tag를 toggle한다. 그리고 lazy tag가 켜져 있는 노드에 대한 접근이 필요할 때, lazy tag를 propagate하면서 해당 노드의 두 자식을 swap한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 노드와 그 노드의 모든 자손 노드들에 대하여 왼쪽 자식과 오른쪽 자식을 swap하면 그 노드에 대응되는 배열이 뒤집어짐을 확인하자.&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;implicit treap은 더 이상 BBST가 아니므로 위와 같은 연산으로 key의 정렬이 무너지거나 하는 것은 신경쓸 필요가 없다. 오히려 가상의 key로 생각하고 있는 개념인 배열의 index를 의도대로 바꾸므로 이는 적절한 사용 방법이다. 또한 이 연산을 통해 Cartesian Tree의 min-heap property가 깨지지도 않음을 확인하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;관련 문제&lt;/span&gt;&lt;/h3&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://www.acmicpc.net/problem/9798&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ9798]&amp;nbsp;Cartesian&amp;nbsp;Tree&lt;/a&gt; &lt;br /&gt;Cartesian Tree 기본 문제이다. Cartesian Tree를 구현해보자.&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://www.acmicpc.net/problem/24960&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ24960]&amp;nbsp;Wireless&amp;nbsp;Communication&amp;nbsp;Network&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;답의 후보가 될 수 있는 경로는 어느 한 노드에서 출발해 점점 높은 위치에 있는 station으로 가다가 다시 점점 낮은 위치에 있는 station으로 이동하는 꼴이 된다.&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;올라가기만 하거나 내려가기만 하는 경로만을 먼저 생각해보자. 어떤 station에서 다음 station으로 이동하는 과정에서 그 사이에 중간 높이의 station이 있는 경우 이 곳을 지나치는 것은 항상 손해이다. 따라서 이러한 관계가 있을 경우 중간 station을 항상 경유해야 함을 알 수 있다. 이와 같은 경로를 파악하는 것은 Cartesian Tree를 활용해 간단하게 할 수 있다.&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;위 관찰에서 나아가 아래에서 위로 올라갔다가 다시 내려가는 경로의 최장거리를 계산해 문제를 해결하자. 참고로 이 경우 Cartesian Tree의 에지만을 따라 움직이는 경로만을 고려한다면 최장거리를 항상 찾지는 못할 것이다.&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://www.acmicpc.net/problem/8987&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ8987] 수족관 3&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제의 상황을 가장 얕은 곳에서부터 수평하게 자른 영역을 노드로 하는 트리로 모델링할 수 있다. 이와 같은 모델링은 각 바닥의 높이를 배열로 삼는 Cartesian Tree로 표현할 수 있다. 단, 같은 깊이의 바닥이 여럿 있으므로 이 부분에서 Cartesian Tree가 어떻게 구성되는지에 신경을 쓸 필요가 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/18984&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ18984] RMQ Similar Sequence&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;두 배열이 RMQ Similar이라는 것은 두 배열의 Cartesian Tree의 모양이 똑같이 구성된다는 것과 동치이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 문제에서 확률과 관련된 부분을 다루기 어려울 수 있는데, 다음과 같이 접근해보자.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(1) 먼저 구간 \([0,1]\)에서 \(n\)개의 실수를 independent하고 uniform하게 뽑는다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(2) 이 수들을 나열하여 같은 Cartesian Tree를 얻을 수 있는 배열을 얻을 확률과 이 수들의 합의 기댓값을 곱한 것이 문제의 답이 된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;가중치를 무작위로 부여한 treap의 높이가 \(O(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;&lt;a href=&quot;https://www.acmicpc.net/problem/16994&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ16994] 로프와 쿼리&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제는 rope 자료구조를 이용하여 해결할 수 있지만, implicit treap으로도 충분히 해결할 수 있다. 문자열을 배열로 생각하여 implicit treap으로 표현할 수 있고, merge와 split을 이용해 주어지는 쿼리를 처리할 수 있기 때문이다.&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://www.acmicpc.net/problem/3344&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ3344] Robotic Sort&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;implicit treap에서 뒤집기 연산을 구현하여 문제에 주어진 내용을 따라 그대로 시뮬레이션하면 된다.&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://www.acmicpc.net/problem/17607&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ17607] 수열과 쿼리 31&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;implicit treap의 각 노드에 그 노드를 root로 갖는 부분 treap의 정보를 관리하여 구간 쿼리를 해결해보자. 뒤집기 연산이 없을 때에도 이 문제에서 요구하는 쿼리를 처리하는 방법을 잘 모르겠다면 금광 세그에 대해 공부해보는 것을 추천한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고 자료&lt;/h3&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://en.wikipedia.org/wiki/Cartesian_tree&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://en.wikipedia.org/wiki/Cartesian_tree&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Random_binary_tree&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://en.wikipedia.org/wiki/Random_binary_tree&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Treap&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://en.wikipedia.org/wiki/Treap&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://cp-algorithms.com/data_structures/treap.html#implicit-treaps&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://cp-algorithms.com/data_structures/treap.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://usaco.guide/adv/treaps&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://usaco.guide/adv/treaps&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.google.com/presentation/d/14xgtdDWnIBwmJRAuIdZ8FvLZcX9uRxnNoGOGAQRDIvc/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.google.com/presentation/d/14xgtdDWnIBwmJRAuIdZ8FvLZcX9uRxnNoGOGAQRDIvc/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CP자료모음</category>
      <category>Cartesian Tree</category>
      <category>Data Structure</category>
      <category>Implicit Treap</category>
      <category>Randomized Binary Search Tree</category>
      <category>Randomized BST</category>
      <category>Treap</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2369</guid>
      <comments>https://measurezero.tistory.com/2369#entry2369comment</comments>
      <pubDate>Tue, 13 Jan 2026 10:00:00 +0900</pubDate>
    </item>
    <item>
      <title>[PS/CP 공부기록] 02. System of Difference Constraints</title>
      <link>https://measurezero.tistory.com/2368</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;※ 이 공부기록은 부정확한 내용이 다수 포함되어있을 수 있다. 또한 이 글은 정보 전달이 주 목적이 아닌 개인적인 기록용이어서 체계적으로 내용이 정리되어 있지 않을 수 있다. 읽는이의 양해를 바란다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이전 공부기록에 이어서, 그래프에서의 relaxation 개념과 더욱 친숙해지고자 이 주제를 고르게 되었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;개요&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번 글에서 살펴볼 주제는 System of Difference Constraints이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;일반적으로 LP(Linear Programming; 선형계획법)은 PS/CP의 범위에서 쉽게 해결하기 어렵다. 그러나 몇몇 형태의 LP 문제는 풀기 쉬운 다른 형태의 문제로 변형할 수 있다.&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;이 글에서는 해결하기 쉬운 LP 문제의 형태 중 하나인 system of difference constraints를 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;System of Difference Constraints&lt;/h3&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;b&gt;System of Difference Constraints&lt;/b&gt;는 변수 \(x_1\), \(x_2\), \(\cdots\), \(x_n\)와 상수 \(c_1\), \(c_2\), \(\cdots\), \(c_m\)에 대하여 \(x_j-x_i\le c_k\)꼴의 여러 부등식으로 구성된 연립부등식을 말한다. \(c_k\)는 음수일 수도 있다.&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;변수와 부등식의 개수가 많아지면 주어진 연립부등식의 해가 존재하는지조차 빠르게 판단하기 어려워진다. 따라서, system of difference constraints를 푼다는 것은 일반적으로 (1)해의 존재성을 판단하고, 해가 존재한다면 (2) 실제로 가능한 해를 하나 구하는 것을 뜻한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Constraints Graph&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 system of difference constraints에 대응되는 그래프인 &lt;b&gt;Constraints Graph&lt;/b&gt;를 만들자. 이 그래프는 weighted directed graph이다.&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) 각각의 변수 \(x_i\)에 대응되는 정점 \(v_i\)를 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2) 각각의 Difference Constraint \(x_j-x_i\le c_k\)에 대응되는 간선을 생성한다. 이 간선은 정점 \(v_i\)에서 \(v_j\)로 향하는 가중치 \(c_k\)의 간선이다.&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;두 정점 \(v_p\)와 \(v_q\)에 대하여 \(v_p\)에서 \(v_q\)로 향하는 경로 \(P\)에 대응되는 difference constraints를 나열하고 이들을 모두 합하면 \(\displaystyle x_q-x_p\le \sum c_k\)가 된다. 이 때 \(\displaystyle \sum c_k\)는 경로에 포함된 간선의 가중치의 합을 나타낸다.&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;위의 관찰에 따라, 서로 다른 두 변수 \(x_i\)와 \(x_j\)에 대하여 \(v_i\)에서 \(v_j\)로 도달 가능한 경로가 존재하는 경우, \(x_j-x_i\)의 값은 항상 \(v_i\)에서 \(v_j\)로의 최단 경로의 길이 이하임을 알 수 있다. 따라서 \(x_i\)들의 값은 constraints graph에서의 최단거리 제약을 만족해야 함을 알 수 있다.&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;한편, 위 constraints graph에 가상의 정점 \(v'\)을 하나 추가하고, \(v'\)에서 각각의 \(v_i\)로 가중치 0인 간선을 그어보자. 이 때, \(v'\)로부터 \(v_i\)까지의 최단거리는 앞서 살펴본 constraints graph에서의 최단거리 제약을 모두 만족함을 어렵지 않게 알 수 있다. (최단거리의 성질과 relaxation에 대해 잘 생각해보자.)&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;두 정점 사이에 최단경로가 존재하지 않는 경우는 두 가지가 있는데, 하나는 \(v_i\)에서 \(v_j\)로 도달 가능한 경로가 존재하지 않는 경우이고 다른 하나는 경로 중간에 negative cycle이 존재하는 경우이다. 경로가 존재하지 않는 경우, \(v_j-v_i\)에 대한 \(v_j-v_i\le c_k\)꼴의 제약이 없다는 뜻이므로 그 값은 어떤 값이 되어도 상관없다. 반면 negative cycle이 존재하는 경우, (엄밀한 표현은 아니지만) \(v_j-v_i\le -\infty\)가 성립한다는 뜻이므로 주어진 system of difference constraints는 해가 없음을 알 수 있다.&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;문제 특성상 가중치가 음수인 간선이 등장하고 negative cycle도 발견해야 하므로, 음수 간선이 있는 경우에도 효과적으로 동작하는 최단거리 알고리즘을 활용해야 한다. 일반적으로 PS/CP에서 system of difference constraints를 풀 때에는 Bellman-Ford를 이용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;식 정리 패턴&lt;/h3&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/CP에서 볼 수 있는 system of difference constraints 유형의 문제에서 식을 정리할 때 쓰이는 몇 가지 패턴을 소개한다. 각각만 놓고 보면 당연해 보이지만, 처음 보거나 익숙하지 않으면 떠올리기 어려울 수 있으니 유의하자.&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;(0) \(x_j-x_i\ge k\) 형태&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 부등식은 \(x_i-x_j\le -k\)와 같이 변형이 가능하다.&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) \(x_j-x_i=c_k\) 형태, \(x_i=x_j\) 형태&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;종종 연립부등식으로 나타나는 조건과 함께 \(x_j-x_i=c_k\) 형태의 등식을 같이 만족시키는 해를 찾을 것을 요구하는 문제가 출제된다. 이 경우 해당 등식을 \(x_j-x_i\le c_k\)와 \(x_j-x_i\ge c_k\)의 두 부등식으로 나누어 생각해 조건을 difference constraints로 변형할 수 있다.&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;물론 \(x_i=x_j\) 또한 \(c_k\)가 0인 특수한 경우로 보고 똑같이 변형할 수 있다.&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;(2) \(x_i\le k\) 형태, \(x_j\ge k\) 형태&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;0을 나타내는 가상의 정점 \(v\)를 하나 생각하면, \(x_i\le k\)는 \(x_i-0\le k\), \(x_j\ge k\)는 \(x_j-0\ge k\)이므로 \(v\)를 활용한 그래프 모델링을 생각할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(3) 구간합&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;\(x_i+x_{i+1}+\cdots+x_j\le k\) 꼴의 부등식으로 이루어진 연립부등식은 이 글에서 설명한 system of difference constraints의 형태는 아니다. 그러나 \(x_i\)의 prefix sum인 나타내는 \(p_i\)를 이용하면 위 연립부등식을 \(p_j-p_{i-1}\le k\) 꼴의 \(p_i\)에 대한 system of difference constraints로 변형할 수 있다.&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;\(\frac{x_j}{x_i}\le c_k\)꼴의 연립부등식이 주어진다면 양변에 로그를 취해 \(\log x_j-\log x_i \le \log c_k\)의 형태로 변형할 수 있다. 이는 \(\log x_i\)들에 대한 difference constraints로 생각할 수 있다.&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://www.luogu.com.cn/problem/P4926&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[LuoguP4926] 倍杀测量者&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;관련 문제&lt;/h3&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://www.acmicpc.net/problem/2081&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ2081] 저울추&lt;/a&gt;&lt;br /&gt;직관적으로 system of difference constraints 문제임을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구한 저울추의 질량이 각각 1 이상 1000000 이하의 정수여야 한다는 조건까지 그래프 모델링에 포함시켜 구현하자.&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://www.acmicpc.net/problem/7634&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ7634] Guessing Game&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;\(b'_i=-b_i\)로 설정하면 지문의 모든 제약을 difference constraints로 바꾸는 데에 어려움이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/4109&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ4109] The Ninja Way&lt;/a&gt;&lt;br /&gt;수직선에서 인접한 두 트리 사이의 위치관계와 크기순으로 인접한 두 트리 사이의 위치관계를 difference constraints로 표현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 낮은 나무의 좌표를 \(x_l\), 가장 높은 나무의 좌표를 \(x_h\)라 하자. 이 때, \(x_l&amp;lt;x_h\)라면 \(x_l-x_h\)의 최댓값이 문제의 답이 되고, \(x_h&amp;lt;x_l\)이라면 \(x_h-x_l\)의 최댓값이 문제의 답이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/27400&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ27400] Restore Array&lt;/a&gt;&lt;br /&gt;prefix sum을 이용하면 구간의 정보를 difference constraints의 형태로 변형할 수 있다.&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://www.acmicpc.net/problem/29765&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ29765] PAndOrA&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정수의 and 및 or 연산의 결과는 각 비트마다 독립적이므로 비트별로 문제를 해결해도 상관없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 비트 별로 주어진 구간 쿼리를 만족시키려면 켜져 있는 비트의 개수가 어때야 하는지에 대한 제약을 prefix sum에 대한 difference constraints로 나타내는 것으로 비트별 문제를 해결할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/7332&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ7332] 편의점 알바&lt;/a&gt;&lt;br /&gt;많은 문제가 그렇듯이, 단조성이 있다면 이분 탐색을 같이 활용할 수 있다. 이 문제에서는 \(x\)명을 고용해 필요한 알바 수를 충족시킬 수 있다면 더 많은(물론 \(N\) 이하) 알바를 고용해도 충족이 항상 가능할 것이고, \(x'\)명을 고용해서는 필요한 알바 수를 충족시킬 수 없다면 더 적은 알바를 고용해도 충족이 항상 불가능하다는 점을 활용하여 이분탐색을 진행할 수 있다.&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://www.acmicpc.net/problem/19153&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ19153] Jordan&lt;/a&gt;&lt;br /&gt;본문에 직접적으로 적힌 조건 중에는 이렇다 할 부등식 형태의 조건이 보이지 않는다. 그러나 이 문제 역시 prefix sum을 이용해 나타낸 difference constraints 형태로 나타내 해결할 수 있다.&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;h3 data-ke-size=&quot;size23&quot;&gt;참고 자료&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CLRS 24.4&lt;/p&gt;</description>
      <category>CP자료모음</category>
      <category>bellman-ford</category>
      <category>Difference Constraints</category>
      <category>graph theory</category>
      <category>Linear Programming</category>
      <category>shortest path</category>
      <category>System of Difference Constraints</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2368</guid>
      <comments>https://measurezero.tistory.com/2368#entry2368comment</comments>
      <pubDate>Wed, 7 Jan 2026 10:00:30 +0900</pubDate>
    </item>
    <item>
      <title>[PS/CP 공부기록] 01. Minimum Steiner Tree in Graph</title>
      <link>https://measurezero.tistory.com/2367</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;공부기록을 남기기 시작하며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음으로 오프라인 대회(Good Bye, BOJ 2025!)를 나가고 느낀 점이 있다.&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;하지만 쉬운 문제도 빠르게 못 풀던 예전과는 다르게, 이제는 단순히 쉬운 문제를 반복해서 푸는 것보다 다양한 이론, 알고리즘, 자료구조를 접하고, 구현 실력이 필요한 문제 또한 시도해 대회 환경에서 더 많은 문제를 푸는 것을 목표로 해도 될 것 같는 생각이 들었다.&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;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;이번 글에서 살펴볼 주제는 Minimum Steiner Tree in Graph이다.&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/CP에서 찾아볼 수 있는 Minimum Steiner Tree 문제는 크게 둘로 구분지어 생각할 수 있는 것 같다. 그 중 하나는 그래프에서 정의되어 있고, 다른 하나는 좌표평면 위의 점과 유클리드 거리에 대하여 정의되어 있다. 이 글에서는 전자의 문제를 다룬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Minimum Steiner Tree in Graph&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Minimum Steiner Tree가 무엇인지 알아보자.&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;정점의 집합 \(V\)와 (음이 아닌) 가중치가 있는 간선의 집합 \(E\)로 구성된 연결그래프 \(G\)에 대하여, \(V\)의 (공집합이 아닌) 부분집합을 \(K\)라 하자. 이 때, &lt;b&gt;Steiner Tree&lt;/b&gt;는 \(K\)의 모든 정점을 포함하는 \(G\)의 subtree를 말한다. 또한 &lt;b&gt;Minimum Steiner Tree&lt;/b&gt;는 모든 Steiner Tree 중 간선의 가중치의 합이 가장 작은 Steiner Tree를 말한다. 또한 \(K\)에 속한 정점들을 &lt;b&gt;Terminal&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;\(K\)의 크기가 1이면 Minimum Steiner Tree는 그 정점만을 포함한 가중치 0의 subtree가 됨은 자명하다. 또한 \(K\)의 크기가 2이면 두 정점을 잇는 최단경로의 형태가 Minimum Steiner Tree가 될 것이며, \(K\)의 크기가 3이면 각 정점에 대하여 세 정점으로부터의 최단거리의 합을 구했을 때 그 합이 가장 작은 정점을 중심으로 \(K\)의 세 정점을 최단거리로 이은 형태가 Minimum Steiner Tree가 될 것이다. 그러나 \(K\)의 크기가 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;이 문제는 Minimum Spanning Tree를 구하는 문제의 일반화로 생각할 수 있다. \(K=V\)인 경우 Minimum Spanning Tree와 정확히 같은 문제가 됨을 관찰하자. 그러나 이 문제는 NP-Hard 문제임이 잘 알려져있다. 따라서 P=NP가 아닌 이상에는 Minimum Steiner Tree 문제를 다항시간 내에 해결하기 어려울 것이다.&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;Minimum Steiner Tree를 구하기 위한 다양한 approximation algorithm이 존재하지만 이러한 알고리즘은 PS/CP에서 나오는 문제들과 잘 맞지 않는다. 일반적으로 PS/CP에서는 정확한 해를 구할 것을 요구하는 문제가 주로 나오기 때문이다. 따라서 이 글에서는 Exact Minimum Steiner Tree를 구하는 내용만을 다룬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Dreyfus-Wagner Algorithm&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Minimum Steiner Tree를 구하는 알고리즘으로 &lt;b&gt;Dreyfus-Wagner&lt;/b&gt; 알고리즘이 잘 알려져 있다. 이 알고리즘은 다음과 같은 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;\(K\)의 부분집합 \(K'\)과 \(V\)에 속하는 정점 \(v\)에 대하여 \(dp[K'][v]\)를 \(K'\)에 속한 모든 정점을 포함하면서 \(v\) 또한 포함하는 가장 가중치가 작은 \(G\)의 서브트리의 가중치로 정의하자. 즉, \(dp[K'][v]\)는 \(K'\cup\{v\}\)에 대한 Minimum Steiner Tree의 가중치를 나타낸다.&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;이 때, \(dp[K'][v]\)의 값은 다음과 같은 두 가지 방식으로 다른 상태로부터 계산할 수 있다.&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) 이 상태를 나타내는 Minimum Steiner Tree에서 \(v\)의 degree가 2 이상인 경우:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;\(K'\)을 두 집합 \(K_1\)과 \(K_2\)로 나누면 \(dp[K'][v]=dp[K_1][v]+dp[K_2][v]\)가 되게끔 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2) 이 상태를 나타내는 Minimum Steiner Tree에서 \(v\)의 degree가 1인 경우:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 Minimum Steiner Tree에서 \(v\)와 연결된 정점을 \(u\)라 하고, 그 연결 간선의 가중치를 \(w\)라 하자. 이 때 \(dp[K'][v]=dp[K'][u]+w\)가 성립한다.&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)의 경우, \(K'\)의 모든 부분집합에 대하여 문제를 먼저 해결해둔다면 계산을 쉽게 처리할 수 있다. 다만 이 값을 계산하는 시점에는 \(K'\)를 어떻게 쪼개야 최적인지에 대한 정보를 가지고 있지 않으므로, 가능한 모든 방법으로 \(K'\)를 쪼개봐야 한다는 점에 유의하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2)의 경우, 이 과정을 degree가 1이 아닌 정점이 나올 때까지 반복하면 \(K'\)의 Minimum Steiner Tree를 얻게 됨을 관찰하자. 따라서, \(K'\)의 Minimum Steiner Tree의 값에서부터 relaxation을 반복하여 값을 갱신해나가는 것으로 (2)의 계산을 처리할 수 있다. 이해하기 어렵다면 Dijkstra 알고리즘의 relaxation 과정을 생각해보자.&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;기저 상태를 각 \(K\)의 원소 \(k\)에 대하여 \(dp[\{k\}][k]\)의 값을 0, 나머지 상태의 값을 전부 (가상의) 무한대로 설정하고 위와 같이 \(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;relaxation 과정에 binary heap을 사용한다고 가정하면, Dreyfus-Wagner 알고리즘의 시간복잡도는 \(O(V3^K+(V+E)2^K\log V)\)와 같다. 여기서 \(V3^K\)는 DP에서의 (1)의 처리에 드는 시간복잡도이고, \((V+E)2^K\log V\)는 DP에서의 (2)의 처리에 드는 시간복잡도이다. (1)의&amp;nbsp; 시간복잡도를 이해하기 어렵다면, bitmasking으로 \(K\)까지의 정수가 나타내는 각 집합에 대하여 각각의 부분집합의 개수의 합이 \(O(3^K)\)과 같다는 내용을 찾아보도록 하자. (참고: &lt;a href=&quot;https://measurezero.tistory.com/2377&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공부기록07&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;따라서 Dreyfus-Wagner 알고리즘은 \(V\)와 \(E\)가 어느 정도 크더라도 \(K\)가 충분히 작다면 PS/CP에서 충분히 제한 내에 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Dreyfus-Wagner 알고리즘 구현 팁&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bitmask를 활용하여 표현된 어떤 집합을 나타내는 정수\(x\)가 있을 때, \(x\)의 모든 부분집합은 초기값이 \(x\)인 변수 \(x'\)에 대하여 \(x':=(x'-1)\&amp;amp;x\)를 반복하는 것으로 모두 접근할 수 있다.&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;Dijkstra 알고리즘을 문제 제한에 따라 다양한 방식으로 최적화할 수 있듯이, 위 알고리즘의 중간 과정인 relaxation은 문제 상황에 따라 문제에 따라 다양하게 변형해 복잡도를 낮출 수 있다. 예를 들어 노드가 적지만 dense한 그래프의 경우 우선순위 큐를 사용하지 않고 \(O(N^2)\)으로 relaxation을 처리할 수 있다. 또한 가중치의 크기가 매우 제한적이라면 Dial 알고리즘처럼 버킷을 활용해 relaxation을 처리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Rectilinear Minimum Steiner Tree&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Rectilinear Minimum Steiner Tree 문제는 좌표평면 위에 \(K\)개의 점이 주어졌을 때 이 점들을 축과 평행한 선분만으로 모두 연결하기 위해 그어야 하는 선분의 길이의 최솟값을 구하는 문제이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;주어진 \(K\)개의 점에 대하여 두 축과 각각 평행한 직선을 그려 얻게 되는 격자형태의 그래프를 Hanan Grid라고 부른다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;\(K\)개의 점에 대한 Rectilinear Minimum Steiner Tree는 이 \(K\)개의 정점으로 생성된 Hanan Grid 위에서 항상 찾을 수 있다. 따라서 \(K\)가 충분히 작을 경우 Dreyfus-Wagner 알고리즘으로 Rectilinear Minimum Steiner Tree 문제 또한 해결할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;여담&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Dijkstra 알고리즘처럼 relaxation을 진행하는 아이디어는 Dreyfus-Wagner 알고리즘에서만 사용되는 것이 아닌 다른 많은 문제에서도 종종 사용되는 트릭이니 잘 기억해두자.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;관련 문제&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/12752&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ12752] 도시들&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;직관적인 문제이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc364/tasks/abc364_g&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[ABC364G] Last Major City&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Dreyfus-Wagner 알고리즘의 각 DP값의 의미를 잘 생각해보자. 각 경우에 대하여 Dreyfus-Wagner 알고리즘을 써야만 이 문제를 해결할 수 있을까?&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc395/tasks/abc395_g&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[ABC395G] Minimum Steiner Tree 2&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위 문제의 확장판이다. 주어지는 그래프가 complete graph이므로 \(O(N^2)\) relaxation을 진행하는 것이 좋다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc395/tasks/abc395_g&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[CF152E] Garden&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 문제는 Minimum Steiner Tree 문제와 비슷하게 생겼지만 조금 다르다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;바로 주어진 문제 상황을 그래프로 모델링했을 때, 최소화해야 하는 가중치가 간선이 아닌 정점에 있다는 점이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그러나 Dreyfus-Wagner 알고리즘을 조금만 변형하면 이에 대응되는 문제 또한 해결할 수 있으니 잘 생각해보자.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또한 이 문제에서는 최소 가중치를 구하는 데에서 그치지 않고 해를 역추적하여 구성해야 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/34144&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[BOJ34144] 크로스링크&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;처음과 마지막 행 및 열에 속한 정점이 적어도 하나씩은 속한 트리 중 정점의 가중치 총합이 가장 적은 경우를 찾는 문제로 볼 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;비트마다 하나의 정점에만 대응되던 Minimum Steiner Tree에서의 Terminal 대신 특정 행 또는 열에 속한 정점을 포함했는가에 대한 정보를 하나의 비트에 담고 multisource Dijkstra 알고리즘처럼 Dreyfus-Wagner 알고리즘을 진행하는 것으로 문제를 해결할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;각 정점의 가중치가 0 이상 35 이하의 정수로 매우 제약이 강하므로 Dial 알고리즘처럼 버킷을 활용해 시간복잡도를 줄일 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;참고 자료&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Steiner_tree_problem&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://en.wikipedia.org/wiki/Steiner_tree_problem&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Rectilinear_Steiner_tree&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://en.wikipedia.org/wiki/Rectilinear_Steiner_tree&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Hanan_grid&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://en.wikipedia.org/wiki/Hanan_grid&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://codeforces.com/blog/entry/91601&quot;&gt;https://codeforces.com/blog/entry/91601&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kopricky.github.io/code/Academic/steiner_tree.html&quot;&gt;https://kopricky.github.io/code/Academic/steiner_tree.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CP자료모음</category>
      <category>Bitmasking</category>
      <category>Dijkstra</category>
      <category>Dreyfus-Wagner Algorithm</category>
      <category>dynamic programming</category>
      <category>graph theory</category>
      <category>Minimum Steiner Tree</category>
      <category>shortest path</category>
      <category>Tree</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2367</guid>
      <comments>https://measurezero.tistory.com/2367#entry2367comment</comments>
      <pubDate>Fri, 2 Jan 2026 10:00:42 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 27470 // C++] 멋진 부분집합</title>
      <link>https://measurezero.tistory.com/2365</link>
      <description>&lt;div style=&quot;color: #000000; text-align: right;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;color: #000000; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 27470번 문제인 멋진 부분집합이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/27470&quot;&gt;https://www.acmicpc.net/problem/27470&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주어진 원소 중 절반 이상의 원소가 특정 소인수를 포함하고 있다면, 임의의 원소를 뽑았을 때 그 수가 특정 소인수를 포함하고 있을 확률은 50% 이상이다.&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;따라서, 위와 같은 원소 뽑기를 충분히 많이 반복하면 답이 되는 소인수를 포함하는 경우가 나오지 않을 확률은 0에 수렴하게 된다.&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;이 과정에서 이미 확인한 소인수를 다시 확인할 필요는 없으므로, 중복된 확인을 하지 않기 위한 최적화를 같이 진행해주자. 방법은 다양하지만 글쓴이는 주어진 원소에서 확인한 소인수는 나누어주는 방식으로 구현하였다.&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;pre id=&quot;code_1764546259192&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;random&amp;gt;
using namespace std;

bool sieve[32768];
vector&amp;lt;int&amp;gt; vec, ans;

void init() {
    sieve[0] = sieve[1] = 1;
    for (int i = 2; i * i &amp;lt; 32768; i++) {
        if (sieve[i]) continue;
        for (int j = i * i; j &amp;lt; 32768; j += i) sieve[j] = 1;
    }
    for (int i = 2; i &amp;lt; 31622; i++) {
        if (!sieve[i]) vec.emplace_back(i);
    }
}

int N;
int A[500000], AA[500000];
random_device rd;
mt19937 mt(rd());

void func(int i) {
    int cnt = 0;
    for (int k = 0; k &amp;lt; N; k++) {
        if (!(AA[k] % i)) {
            cnt++;
            while (!(AA[k] % i)) AA[k] /= i;
        }
    }
    if (cnt &amp;gt;= (N + 1) / 2) {
        cout &amp;lt;&amp;lt; &quot;YES\n&quot;;
        cnt = (N + 1) / 2;
        for (int k = 0; k &amp;lt; N &amp;amp;&amp;amp; cnt; k++) {
            if (!(A[k] % i)) cnt--, cout &amp;lt;&amp;lt; A[k] &amp;lt;&amp;lt; ' ';
        }
        exit(0);
    }
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    init();

    cin &amp;gt;&amp;gt; N;
    for (int i = 0; i &amp;lt; N; i++) cin &amp;gt;&amp;gt; A[i];
    for (int i = 0; i &amp;lt; N; i++) AA[i] = A[i];
    for (int _ = 0; _ &amp;lt; 40; _++) {
        int x = AA[mt() % N];
        for (auto &amp;amp;i:vec) {
            if (i * i &amp;gt; x) break;
            if (x % i) continue;
            func(i);
            while (!(x % i)) x /= i;
        }
        if (x &amp;gt; 1) func(x);
    }

    cout &amp;lt;&amp;lt; &quot;NO&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>BOJ</category>
      <category>백준 27470</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2365</guid>
      <comments>https://measurezero.tistory.com/2365#entry2365comment</comments>
      <pubDate>Mon, 1 Dec 2025 10:00:07 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 34803 // C++] 문자열 로또</title>
      <link>https://measurezero.tistory.com/2364</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 34803번 문제인 문자열 로또이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/34803&quot;&gt;https://www.acmicpc.net/problem/34803&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;주어진 제한조건에서, 각 문자열에서 얻을 수 있는 길이 \(K\)의 부분문자열은 총 \(L-K+1\)개임을 관찰하자.&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(L-K+1)\)개의 부분문자열 중 가장 많이 등장한 문자열의 등장 횟수를 구하는 문제로 볼 수 있다.&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 이하이고 부분문자열의 총 개수는 2000 이하이므로, 정렬이나 map 등을 이용하여 각 각 부분문자열의 등장 횟수를 구해 문제를 해결할 수 있다.&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;pre id=&quot;code_1764258316930&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;map&amp;gt;
using namespace std;

int L, N, K, ans;
string s[20];
map&amp;lt;string, int&amp;gt; mp;

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; L &amp;gt;&amp;gt; N;
    for (int i = 0; i &amp;lt; N; i++) cin &amp;gt;&amp;gt; s[i];
    cin &amp;gt;&amp;gt; K;
    for (int i = 0; i &amp;lt; N; i++) {
        for (int k = 0; k + K &amp;lt;= L; k++) mp[s[i].substr(k, K)]++;
    }

    for (auto &amp;amp;p:mp) ans = max(ans, p.second);
    cout &amp;lt;&amp;lt; ans;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 34803</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2364</guid>
      <comments>https://measurezero.tistory.com/2364#entry2364comment</comments>
      <pubDate>Fri, 28 Nov 2025 10:00:34 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 26570 // C++] Semiperfect</title>
      <link>https://measurezero.tistory.com/2363</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 26570번 문제인 Semiperfect이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/26570&quot;&gt;https://www.acmicpc.net/problem/26570&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;100만 미만의 양의 정수 \(m\)이 주어질 때, \(m\)의 자기 자신을 제외한 약수 중 일부(전체도 가능)의 합으로 \(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;100만 미만의 정수 중 가장 많은 수는 240개의 약수를 가진다(720720 등). 따라서 \(m\)의 모든 약수를 활용하여 \(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;이러한 유형의 문제를 해결하는 대표적인 방법으로 knapsack dp가 있다. 특히 이 문제에서는 존재의 여부만을 확인하면 되므로 0과 1로 모든 상태를 표현할 수 있고, 따라서 비트셋을 활용한 knapsack 구현이 가능하다. 글쓴이는 이를 활용하여 문제를 해결하였다.&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;pre id=&quot;code_1764167707571&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;bitset&amp;gt;
using namespace std;

int T;
bitset&amp;lt;1000000&amp;gt; st;

bool func(int n) {
    st = 0;
    st[0] = 1;
    for (int i = 1; i * i &amp;lt;= n; i++) {
        if (n % i) continue;
        if (i != n) st = (st &amp;lt;&amp;lt; i) | st;
        if (n / i != n &amp;amp;&amp;amp; n / i != i) st = (st &amp;lt;&amp;lt; (n / i)) | st;
    }
    return st[n];
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; T;
    while (T--) {
        int x; cin &amp;gt;&amp;gt; x;
        if (func(x)) cout &amp;lt;&amp;lt; &quot;Semiperfect\n&quot;;
        else cout &amp;lt;&amp;lt; &quot;NOT Semiperfect\n&quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>BOJ</category>
      <category>백준 26570</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2363</guid>
      <comments>https://measurezero.tistory.com/2363#entry2363comment</comments>
      <pubDate>Thu, 27 Nov 2025 10:00:03 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 34679 // C++] 홍수</title>
      <link>https://measurezero.tistory.com/2362</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 34679번 문제인 홍수이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/34679&quot;&gt;https://www.acmicpc.net/problem/34679&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;주어진 문제의 상황을 각 하수구를 노드로, 물이 흐를 수 있는 두 하수구의 관계를 방향이 있는 에지로 하는 그래프로 모델링해보자. 이와 같은 모델에서 주어진 문제는 그래프의 모든 노드에서 입력으로 주어진 &quot;물이 고이지 않는 하수구&quot; 노드 중 적어도 하나에 도달할 수 있는지를 판단하는 것으로 해결할 수 있게 된다.&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;그러나 각 노드마다 &quot;물이 고이지 않는 하수구&quot;에 도달할 수 있는지 한 번씩 탐색해보는 것은 비효율적이다. 특히, 잘 구성된 일자 그래프가 주어지면 시간초과가 발생하게 된다. 다른 방법을 생각해보자.&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;각 노드에서 어떤 &quot;물이 고이지 않는 하수구&quot;에 도달할 수 있다는 것은, 주어진 그래프와 간선의 방향을 뒤집은 그래프(Transpose Graph라고 부른다)에서 각 노드마다 도달할 수 있는 &quot;물이 고이지 않는 하수구&quot;가 적어도 하나씩 있다는 것과 같다는 점을 관찰하자. 따라서, 주어진 &quot;물이 고이지 않는 하수구&quot; 노드로부터 도달할 수 있는 노드의 집합이 전체 노드의 집합과 같은지를 판단하는 것으로 문제를 해결할 수 있다.&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;이와 같은 문제 상황은 multisource BFS 등으로 어렵지 않게 해결할 수 있다. 글쓴이는 그냥 스택을 이용하여 그래프 탐색을 진행하였다.&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;아래는 제출한 소스코드이다.&lt;/p&gt;
&lt;pre id=&quot;code_1764055363339&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;
using namespace std;

int N, M, K;
int H[200001];
bool visited[200001];
vector&amp;lt;int&amp;gt; G[200001];
vector&amp;lt;int&amp;gt; stk;

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; N &amp;gt;&amp;gt; M;
    for (int i = 1; i &amp;lt;= N; i++) cin &amp;gt;&amp;gt; H[i];
    while (M--) {
        int x, y; cin &amp;gt;&amp;gt; x &amp;gt;&amp;gt; y;
        if (H[x] &amp;gt; H[y]) G[y].emplace_back(x);
        else if (H[x] &amp;lt; H[y]) G[x].emplace_back(y);
    }

    cin &amp;gt;&amp;gt; K;
    while (K--) {
        int x; cin &amp;gt;&amp;gt; x;
        visited[x] = 1;
        stk.emplace_back(x);
    }
    while (!stk.empty()) {
        int x = stk.back(); stk.pop_back();
        for (auto &amp;amp;nxt:G[x]) {
            if (visited[nxt]) continue;
            visited[nxt] = 1;
            stk.emplace_back(nxt);
        }
    }
    for (int i = 1; i &amp;lt;= N; i++) {
        if (!visited[i]) cout &amp;lt;&amp;lt; &quot;flood&quot;, exit(0);
    }
    cout &amp;lt;&amp;lt; &quot;no flood&quot;;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 34697</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2362</guid>
      <comments>https://measurezero.tistory.com/2362#entry2362comment</comments>
      <pubDate>Wed, 26 Nov 2025 10:00:34 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 29338 // C++] Склад Оби-Вана Кеноби</title>
      <link>https://measurezero.tistory.com/2358</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 글쓴이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 29338번 문제인 Склад&amp;nbsp;Оби-Вана&amp;nbsp;Кеноби이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/29338&quot;&gt;https://www.acmicpc.net/problem/29338&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;앞 절반의 원소를 들고 있는 덱과 뒤 절반의 원소를 들고 있는 덱을 관리하자.&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(1)\)에 어렵지 않게 처리할 수 있게 되므로 문제를 간단히 해결할 수 있다.&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;pre id=&quot;code_1743639273694&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;deque&amp;gt;
#include &amp;lt;utility&amp;gt;
#include &amp;lt;string&amp;gt;
using namespace std;

int Q;
deque&amp;lt;int&amp;gt; L, R;

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; Q;
    while (Q--) {
        string s; cin &amp;gt;&amp;gt; s;
        if (s.front() == 'a') {
            int x; cin &amp;gt;&amp;gt; x;
            R.push_back(x);
        }
        else if (s.front() == 't') {
            if (!R.empty()) R.pop_back();
        }
        else swap(L, R);
        while (L.size() &amp;gt; R.size()) R.push_front(L.back()), L.pop_back();
        while (L.size() + 1 &amp;lt; R.size()) L.push_back(R.front()), R.pop_front();
    }

    cout &amp;lt;&amp;lt; L.size() + R.size() &amp;lt;&amp;lt; '\n';
    for (auto &amp;amp;x:L) cout &amp;lt;&amp;lt; x &amp;lt;&amp;lt; ' ';
    for (auto &amp;amp;x:R) cout &amp;lt;&amp;lt; x &amp;lt;&amp;lt; ' ';
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 29338</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2358</guid>
      <comments>https://measurezero.tistory.com/2358#entry2358comment</comments>
      <pubDate>Thu, 3 Apr 2025 10:00:29 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 29343 // C++] Шифровка</title>
      <link>https://measurezero.tistory.com/2357</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 글쓴이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 29343번 문제인 Шифровка이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/29343&quot;&gt;https://www.acmicpc.net/problem/29343&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;주어지는 문자열에 대하여, 1 이상 전체 길이 이하의 같은 길이의 접두사와 접미사 쌍 중 1개 이상의 모음을 포함하면서 같은 개수의 모음을 포함하는 쌍의 개수를 구하는 문제이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이는 각 접두사마다 모음이 몇 개 있는지, 접미사마다 모음이 몇 개 있는지를 이용해 어렵지 않게 계산할 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;길이가 \(k&amp;gt;1\)인 접두사에 포함된 모음의 개수는 길이가 \(k-1\)인 접두사에 포함된 모음의 개수와 \(k\)번째 문자의 모음 여부로 구할 수 있다는 점을 활용하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아래는 제출한 소스코드이다.&lt;/p&gt;
&lt;pre id=&quot;code_1743552623162&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;string&amp;gt;
using namespace std;

int N, val1, val2, ans;
string s;
int isvowel[128];

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    isvowel['a'] = isvowel['e'] = isvowel['i'] = isvowel['o'] = isvowel['u'] = 1;
    isvowel['A'] = isvowel['E'] = isvowel['I'] = isvowel['O'] = isvowel['U'] = 1;
    cin &amp;gt;&amp;gt; N &amp;gt;&amp;gt; s;
    for (int L = 0, R = N - 1; L &amp;lt; N; L++, R--) {
        val1 += isvowel[s[L]], val2 += isvowel[s[R]];
        if (val1 &amp;amp;&amp;amp; val1 == val2) ans++;
    }
    cout &amp;lt;&amp;lt; ans;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>BOJ</category>
      <category>백준 29343</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2357</guid>
      <comments>https://measurezero.tistory.com/2357#entry2357comment</comments>
      <pubDate>Wed, 2 Apr 2025 10:00:07 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 33689 // C++] CPDU</title>
      <link>https://measurezero.tistory.com/2356</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 글쓴이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 33689번 문제인 CPDU이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/33689&quot;&gt;https://www.acmicpc.net/problem/33689&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;입력으로 주어지는 문자열 중 문자 '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;string으로 입력을 받고 그 첫번째 문자를 확인하는 것으로 위의 작업을 간단하게 구현할 수 있다.&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;pre id=&quot;code_1743466834954&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
using namespace std;

int T, ans;
string s;

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; T;
    while (T--) {
        cin &amp;gt;&amp;gt; s;
        if (s.front() == 'C') ans++;
    }
    cout &amp;lt;&amp;lt; ans;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 33689</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2356</guid>
      <comments>https://measurezero.tistory.com/2356#entry2356comment</comments>
      <pubDate>Tue, 1 Apr 2025 10:00:02 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 33690 // C++] 포린드롬</title>
      <link>https://measurezero.tistory.com/2355</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 글쓴이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 33690번 문제인 포린드롬이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/33690&quot;&gt;https://www.acmicpc.net/problem/33690&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;먼저 한 자리 수(0 포함)는 주어진 조건을 항상 만족함을 관찰하자.&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;주어지는 조건을 만족하는 한 자리 이상의 수는 &quot;두 자리 이상이면서 마지막 자리를 지운 수와 원래 수가 모두 팰린드롬인 수&quot;로 해석할 수 있다. 한편, 두 수에서 서로 대응되는 위치에 있는 각 숫자의 쌍을 연결하면 이 조건을 만족하는 수를 구성하는 숫자가 모두 동일해야 함을 알 수 있다.&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\) 이하의 수 중 이러한 수를 모두 생성하는 것으로 주어진 수 이하인지 확인하는 것으로 문제를 해결할 수 있다. 이는 111...1 꼴의 수에 1 이상 9 이하의 정수를 곱하는 것으로 모두 얻을 수 있다.&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;pre id=&quot;code_1743379648316&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
using namespace std;

int N, ans = 1;

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    
    cin &amp;gt;&amp;gt; N;
    for (int i = 1; i &amp;lt; 1000000000; i = i * 10 + 1) {
        for (int k = 1; k &amp;lt; 10; k++) {
            if (i * k &amp;lt;= N) ans++;
        }
    }
    cout &amp;lt;&amp;lt; ans;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>BOJ</category>
      <category>백준 33690</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2355</guid>
      <comments>https://measurezero.tistory.com/2355#entry2355comment</comments>
      <pubDate>Mon, 31 Mar 2025 10:00:56 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 21275 // C++] 폰 호석만</title>
      <link>https://measurezero.tistory.com/2354</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 글쓴이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 21275번 문제인 폰 호석만이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/probelm/21275&quot;&gt;https://www.acmicpc.net/probelm/21275&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;주어진 두 수를 2진수부터 36진수까지 각각의 진법으로 해석한 값을 먼저 계산해두자. 그리고 각각의 진법 쌍에 대하여 두 값이 같은지를 확인해 문제를 해결하자.&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;조건에 따라 두 진법이 서로 같으면 안 되며 각각의 값이 \(2^{63}\) 미만이어야 한다는 점에 유의하자.&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;pre id=&quot;code_1743121213859&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
using namespace std;
typedef long long ll;
typedef __int128 lll;

lll MX = 9223372036854775807LL;
lll A[37], B[37];
string s;
ll aval, aidx, bidx, cnt;

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; s;
    for (int b = 2; b &amp;lt; 37; b++) {
        for (auto &amp;amp;l:s) {
            int x;
            if ('0' &amp;lt;= l &amp;amp;&amp;amp; l &amp;lt;= '9') x = l - '0';
            else x = 10 + l - 'a';
            if (x &amp;gt;= b) {
                A[b] = -1;
                break;
            }
            A[b] = A[b] * b + x;
            if (A[b] &amp;gt; MX) {
                A[b] = -1;
                break;
            }
        }
    }
    cin &amp;gt;&amp;gt; s;
    for (int b = 2; b &amp;lt; 37; b++) {
        for (auto &amp;amp;l:s) {
            int x;
            if ('0' &amp;lt;= l &amp;amp;&amp;amp; l &amp;lt;= '9') x = l - '0';
            else x = 10 + l - 'a';
            if (x &amp;gt;= b) {
                B[b] = -1;
                break;
            }
            B[b] = B[b] * b + x;
            if (B[b] &amp;gt; MX) {
                B[b] = -1;
                break;
            }
        }
    }

    for (int i = 2; i &amp;lt; 37; i++) {
        for (int j = 2; j &amp;lt; 37; j++) {
            if (i != j &amp;amp;&amp;amp; A[i] == B[j] &amp;amp;&amp;amp; A[i] &amp;gt;= 0) aval = A[i], aidx = i, bidx = j, cnt++;
        }
    }

    if (cnt &amp;gt; 1) cout &amp;lt;&amp;lt; &quot;Multiple&quot;;
    else if (cnt == 1) cout &amp;lt;&amp;lt; aval &amp;lt;&amp;lt; ' ' &amp;lt;&amp;lt; aidx &amp;lt;&amp;lt; ' ' &amp;lt;&amp;lt; bidx;
    else cout &amp;lt;&amp;lt; &quot;Impossible&quot;;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 21275</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2354</guid>
      <comments>https://measurezero.tistory.com/2354#entry2354comment</comments>
      <pubDate>Fri, 28 Mar 2025 10:00:22 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 33574 // C++] 끊임없는 정렬과 창조함으로</title>
      <link>https://measurezero.tistory.com/2353</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 글쓴이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 33574번 문제인 끊임없는 정렬과 창조함으로이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/33574&quot;&gt;https://www.acmicpc.net/problem/33574&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;주어지는 쿼리의 수가 3000 이하이므로 각 쿼리가 주어질 때의 배열의 크기 \(N\)은 3000 이하이다. 또한 크기가 \(N\)인 배열을 정렬하는 것은 \(O(N\lg N)\)의 시간복잡도로, 원소를 삽입하는 것은 \(O(N)\)의 시간복잡도로 수행할 수 있으므로 주어진 쿼리를 곧이곧대로 수행하는 시간복잡도는 \(O(N^2\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;더 좋은 복잡도로 문제를 해결할 수도 있다. 정렬을 한번 수행하면 앞서 원소를 어디에 삽입했는지나 정렬을 했었는지 여부에 관계없이 항상 일정한 결과를 얻을 수 있다는 점을 이용하면 정렬을 단 한번만 수행해도 됨을 관찰하자. 또한, 이후 원소삽입과정은 세그먼트 트리 등의 자료구조를 이용하여 각 원소의 삽입될 위치를 관리하는 것으로 한번에 갱신해줄 수 있다.&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;pre id=&quot;code_1743035473061&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;algorithm&amp;gt;
using namespace std;

int Q, N;
int A[3000];

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; Q;
    while (Q--) {
        int q; cin &amp;gt;&amp;gt; q;
        if (q == 1) {
            cin &amp;gt;&amp;gt; q;
            if (q == 1) sort(A, A + N);
            else sort(A, A + N, greater&amp;lt;&amp;gt;());
        }
        else {
            int x, t; cin &amp;gt;&amp;gt; x &amp;gt;&amp;gt; t;
            for (int i = N - 1; i &amp;gt;= x; i--) A[i + 1] = A[i];
            A[x] = t;
            N++;
        }
    }
    cout &amp;lt;&amp;lt; N &amp;lt;&amp;lt; '\n';
    for (int i = 0; i &amp;lt; N; i++) cout &amp;lt;&amp;lt; A[i] &amp;lt;&amp;lt; ' ';
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 33574</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2353</guid>
      <comments>https://measurezero.tistory.com/2353#entry2353comment</comments>
      <pubDate>Thu, 27 Mar 2025 10:00:18 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 29703 // C++] 펭귄의 하루</title>
      <link>https://measurezero.tistory.com/2352</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 글쓴이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 29703번 문제인 펭귄의 하루이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/29703&quot;&gt;https://www.acmicpc.net/problem/29703&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;주어진 문제 상황은 (좌표와 지금까지 물고기서식지를 방문한 적이 있는지 여부)를 정점으로, 이동 가능한 관계를 에지로 하는 그래프에서의 최단거리를 구하는 문제로 생각할 수 있다.&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;위 가중치가 없는 그래프에서의 최단거리를 BFS 등의 방법으로 구해 문제를 해결하자.&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;다른 풀이로, 출발지점과 도착지점에서 각각 다른 물고기서식지까지의 최단거리를 BFS를 통해 구한 다음 양쪽 모두에서 접근할 수 있는 물고기서식지의 두 거리의 합의 최솟값을 구하는 방법이 있다. 이동 방향이 반대여도 이동 거리는 그대로이므로 이러한 풀이가 성립한다는 점을 관찰하자.&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;pre id=&quot;code_1742947751052&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;queue&amp;gt;
using namespace std;

int R, C, sR, sC, eR, eC;
char board[1000][1000];
int dist[1000][1000][2];
queue&amp;lt;pair&amp;lt;int, int&amp;gt;&amp;gt; que;
int dr[4] = {0,0,1,-1};
int dc[4] = {1,-1,0,0};
void bfs() {
    dist[sR][sC][0] = 1;
    que.push(make_pair(sR, sC));
    while (!que.empty()) {
        int r = que.front().first, c = que.front().second, sgn; que.pop();
        if (r &amp;lt; 0) r += R, sgn = 1;
        else sgn = 0;
        for (int i = 0; i &amp;lt; 4; i++) {
            int rr = r + dr[i], cc = c + dc[i];
            if (rr &amp;lt; 0 || rr &amp;gt;= R || cc &amp;lt; 0 || cc &amp;gt;= C || board[rr][cc] == 'D' || dist[rr][cc][sgn]) continue;
            dist[rr][cc][sgn] = dist[r][c][sgn] + 1;
            if (sgn) rr -= R;
            que.push(make_pair(rr, cc));
        }
        if (!sgn &amp;amp;&amp;amp; board[r][c] == 'F') dist[r][c][1] = dist[r][c][0] + 1, que.push(make_pair(r - R, c));
    }
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; R &amp;gt;&amp;gt; C;
    for (int r = 0; r &amp;lt; R; r++) {
        for (int c = 0; c &amp;lt; C; c++) {
            cin &amp;gt;&amp;gt; board[r][c];
            if (board[r][c] == 'S') sR = r, sC = c, board[r][c] = 'E';
            else if (board[r][c] == 'H') eR = r, eC = c, board[r][c] = 'E';
        }
    }

    bfs();
    if (dist[eR][eC][1]) cout &amp;lt;&amp;lt; dist[eR][eC][1] - 2;
    else cout &amp;lt;&amp;lt; -1;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 29703</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2352</guid>
      <comments>https://measurezero.tistory.com/2352#entry2352comment</comments>
      <pubDate>Wed, 26 Mar 2025 10:00:52 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 33656 // C++] Island Exploration</title>
      <link>https://measurezero.tistory.com/2351</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 글쓴이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 33656번 문제인 Island Exploration이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/33656&quot;&gt;https://www.acmicpc.net/problem/33656&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;전형적인 2차원 격자에서의 connected component의 크기를 구하는 문제이다.&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;이 유형의 문제는 flood-fill 계열의 탐색으로 문제를 해결할 수 있다.&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;pre id=&quot;code_1742862603911&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;queue&amp;gt;
using namespace std;

int R, C, sR, sC, ans = 1;
char board[100][100];
int dr[4] = {0,0,1,-1};
int dc[4] = {1,-1,0,0};
queue&amp;lt;pair&amp;lt;int, int&amp;gt;&amp;gt; que;

void bfs() {
    que.push(make_pair(sR, sC));
    board[sR][sC] = '.';
    while (!que.empty()) {
        int r = que.front().first, c = que.front().second; que.pop();
        for (int i = 0; i &amp;lt; 4; i++) {
            int rr = r + dr[i], cc = c + dc[i];
            if (board[rr][cc] != '#') continue;
            board[rr][cc] = '.', ans++;
            que.push(make_pair(rr, cc));
        }
    }
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; R &amp;gt;&amp;gt; C;
    for (int r = 0; r &amp;lt; R; r++) {
        for (int c = 0; c &amp;lt; C; c++) {
            cin &amp;gt;&amp;gt; board[r][c];
            if (board[r][c] == 'S') sR = r, sC = c;
        }
    }

    bfs();
    cout &amp;lt;&amp;lt; ans;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 33656</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2351</guid>
      <comments>https://measurezero.tistory.com/2351#entry2351comment</comments>
      <pubDate>Tue, 25 Mar 2025 10:00:24 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 33638 // C++] Birthday Candles</title>
      <link>https://measurezero.tistory.com/2350</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 글쓴이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 33638번 문제인 Birthday Candles이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/33638&quot;&gt;https://www.acmicpc.net/problem/33638&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;끌 수 있는 촛불의 개수의 형태는 일부는 몇몇 사람의 초는 $k$개를 끄고 나머지 사람의 초는 \(k+1\)개를 끄는 형태뿐임을 관찰하자. (단, 0명도 가능하다.)&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 제출한 소스코드이다.&lt;/p&gt;
&lt;pre id=&quot;code_1742518565364&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;queue&amp;gt;
using namespace std;

int N, H, C, ans;
priority_queue&amp;lt;int, vector&amp;lt;int&amp;gt;, greater&amp;lt;&amp;gt;&amp;gt; pq[100], pqpq;

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; N &amp;gt;&amp;gt; H &amp;gt;&amp;gt; C;
    for (int n = 0; n &amp;lt; N; n++) {
        for (int h = 0; h &amp;lt; H; h++) {
            int x; cin &amp;gt;&amp;gt; x;
            pq[n].push(x);
        }
    }
    for (int h = 0; h &amp;lt; H; h++) {
        for (int n = 0; n &amp;lt; N; n++) pqpq.push(pq[n].top()), pq[n].pop();
        while (!pqpq.empty() &amp;amp;&amp;amp; pqpq.top() &amp;lt;= C) {
            C -= pqpq.top();
            pqpq.pop();
            ans++;
        }
        if (!pqpq.empty()) break;
    }

    cout &amp;lt;&amp;lt; ans;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 33638</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2350</guid>
      <comments>https://measurezero.tistory.com/2350#entry2350comment</comments>
      <pubDate>Fri, 21 Mar 2025 10:00:04 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 33646 // C++] Pencil Crayons</title>
      <link>https://measurezero.tistory.com/2349</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 글쓴이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 33643번 문제인 Pencil Crayons이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/33646&quot;&gt;https://www.acmicpc.net/problem/33646&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;각 박스에 들어있는 크레용의 색이 겹치지 않도록 최소한의 크레용을 빼내자. 이 빼낸 크레용을 이용하여 다시 모든 박스를 서로 다른 색으로 채울 수 있음은 자명하다.&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;위 과정은 set 등의 자료구조를 이용하여 어렵지 않게 구현할 수 있다.&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;pre id=&quot;code_1742431506112&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;string&amp;gt;
#include &amp;lt;set&amp;gt;
using namespace std;

int N, K, ans;
set&amp;lt;string&amp;gt; st;

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; N &amp;gt;&amp;gt; K;
    for (int n = 0; n &amp;lt; N; n++) {
        st.clear();
        for (int k = 0; k &amp;lt; K; k++) {
            string s; cin &amp;gt;&amp;gt; s;
            if (st.count(s)) ans++;
            else st.insert(s);
        }
    }
    cout &amp;lt;&amp;lt; ans;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 33646</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2349</guid>
      <comments>https://measurezero.tistory.com/2349#entry2349comment</comments>
      <pubDate>Thu, 20 Mar 2025 10:00:03 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 33643 // C++] Keys, Phone, Wallet</title>
      <link>https://measurezero.tistory.com/2348</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 글쓴이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 33643번 문제인 Keys, Phone, Wallet이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/33643&quot;&gt;https://www.acmicpc.net/problem/33643&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;주어지는 문자열 중 &quot;keys&quot;, &quot;phone&quot;, &quot;wallet&quot;이 존재하는지를 판단하자. 그리고 등장한 적이 없는 물건을 모두 출력하자. 단, 모든 문자열이 등장한 경우 &quot;ready&quot;를 출력하자.&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;아래는 제출한 소스코드이다.&lt;/p&gt;
&lt;pre id=&quot;code_1742345079815&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
using namespace std;

int N, cnt;
bool iskeys, isphone, iswallet;

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; N;
    while (N--) {
        string s; cin &amp;gt;&amp;gt; s;
        if (s == &quot;keys&quot;) iskeys = 1;
        else if (s == &quot;phone&quot;) isphone = 1;
        else if (s == &quot;wallet&quot;) iswallet = 1;
    }
    if (!iskeys) cout &amp;lt;&amp;lt; &quot;keys\n&quot;;
    if (!isphone) cout &amp;lt;&amp;lt; &quot;phone\n&quot;;
    if (!iswallet) cout &amp;lt;&amp;lt; &quot;wallet\n&quot;;
    if (iskeys &amp;amp;&amp;amp; isphone &amp;amp;&amp;amp; iswallet) cout &amp;lt;&amp;lt; &quot;ready\n&quot;;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 33643</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2348</guid>
      <comments>https://measurezero.tistory.com/2348#entry2348comment</comments>
      <pubDate>Wed, 19 Mar 2025 10:00:26 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 2450 // C++] 모양 정돈</title>
      <link>https://measurezero.tistory.com/2347</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 글쓴이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 2450번 문제인 모양 정돈이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/2450&quot;&gt;https://www.acmicpc.net/problem/2450&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;주어진 모양의 정렬 결과는 각 도형이 등장하는 순서의 경우의 수와 같은 6가지뿐임을 관찰하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 결과로 나올 수 있는 6가지 배치 각각에 대하여 필요한 최소 맞바꾸기 횟수를 구하는 것으로 문제를 해결할 수 있다.&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;각 경우에 대한 최소 맞바꾸기 횟수는 permutation cycle decomposition 개념을 활용하면 어렵지 않게 구할 수 있다.&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;pre id=&quot;code_1742256985371&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;cstring&amp;gt;
#include &amp;lt;algorithm&amp;gt;
using namespace std;

int P[3] = {0,1,2};
bool comp(int &amp;amp;x, int &amp;amp;y) {
    return P[x] &amp;lt; P[y];
}

int N, ans = 0x3f3f3f3f;
int A[100000], B[100000];
int cnt[3][3];

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; N;
    for (int i = 0; i &amp;lt; N; i++) cin &amp;gt;&amp;gt; A[i], A[i]--;
    for (int i = 0; i &amp;lt; N; i++) B[i] = A[i];
    for (int k = 0; k &amp;lt; 6; k++) {
        sort(A, A + N, comp);
        memset(cnt, 0, sizeof(cnt));
        for (int i = 0; i &amp;lt; N; i++) {
            cnt[A[i]][B[i]]++;
        }
        int val1 = min(cnt[0][1], cnt[1][0]);
        cnt[0][1] -= val1, cnt[1][0] -= val1;
        int val2 = min(cnt[0][2], cnt[2][0]);
        cnt[0][2] -= val2, cnt[2][0] -= val2;
        int val3 = min(cnt[1][2], cnt[2][1]);
        cnt[1][2] -= val3, cnt[2][1] -= val3;
        ans = min(ans, val1 + val2 + val3 + (cnt[1][0] + cnt[0][1]) * 2);
        next_permutation(P, P + 3);
    }

    cout &amp;lt;&amp;lt; ans;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 2450</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2347</guid>
      <comments>https://measurezero.tistory.com/2347#entry2347comment</comments>
      <pubDate>Tue, 18 Mar 2025 10:00:31 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 24605 // C++] Tetris Generation</title>
      <link>https://measurezero.tistory.com/2346</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 글쓴이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 24605번 문제인 Tetris Generation이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/24605&quot;&gt;https://www.acmicpc.net/problem/24605&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;7-bag 시스템이 적용된 테트리스에서 주어진 순서대로 테트리스 블록이 생성될 수 있는지 확인하는 문제이다.&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;가능한 각각의 새 bag의 시작점(많아야 7가지)에 대하여 각 bag에 겹치는 블록이 들어있는지 판단해 문제를 해결하자. 이를 구현할 때, 첫 bag과 마지막 bag에는 7개보다 적은 블록이 들어있을 수도 있다는 점에 유의하자.&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;pre id=&quot;code_1742169251934&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;cstring&amp;gt;
using namespace std;

int N;
string s;
int visited[128];

void solve() {
    int ans = 0;
    cin &amp;gt;&amp;gt; s; N = s.length();
    for (int L = 0; L &amp;lt; 7; L++) {
        bool cchk = 1;
        memset(visited, 0, sizeof(visited));
        for (int i = 0; i &amp;lt; min(L, N); i++) {
            if (visited[s[i]]) cchk = 0;
            visited[s[i]] = 1;
        }
        for (int k = L; k &amp;lt; N; k += 7) {
            memset(visited, 0, sizeof(visited));
            for (int i = k; i &amp;lt; min(k + 7, N); i++) {
                if (visited[s[i]]) cchk = 0;
                visited[s[i]] = 1;
            }
        }

        if (cchk) ans = 1;
    }
    cout &amp;lt;&amp;lt; ans &amp;lt;&amp;lt; '\n';
}

int T;

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; T;
    while (T--) solve();
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 24605</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2346</guid>
      <comments>https://measurezero.tistory.com/2346#entry2346comment</comments>
      <pubDate>Mon, 17 Mar 2025 10:00:27 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 14970 // C++] Almost Identical Programs</title>
      <link>https://measurezero.tistory.com/2345</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 글쓴이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 14970번 문제인 Almost Identical Programs이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/14970&quot;&gt;https://www.acmicpc.net/problem/14970&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;주어진 두 문자열이 같으면 IDENTICAL을 출력하면 된다는 점을 확인하자. 아래에서는 두 문자열이 다른 경우만을 생각한다.&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;따옴표의 개수가 같지 않다면 둘은 같을 수가 없다는 점을 관찰하자. 또한 따옴표의 개수가 같은 경우, (올바른 순서를 구성하는) 두 따옴표 사이에 포함된 문자열의 쌍 중 한 쌍만 다른 경우만 답이 CLOSE가 됨을 관찰하자.&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;아래는 제출한 소스코드이다.&lt;/p&gt;
&lt;pre id=&quot;code_1741910858024&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;
using namespace std;

string s1, s2, ss1, ss2, tmp;
vector&amp;lt;string&amp;gt; v1, v2;
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    while (getline(cin, s1) &amp;amp;&amp;amp; getline(cin, s2)) {
        if (s1 == s2) cout &amp;lt;&amp;lt; &quot;IDENTICAL\n&quot;;
        else {
            ss1.clear(), ss2.clear(), v1.clear(), v2.clear();
            int sgn = 0;
            tmp.clear();
            for (auto &amp;amp;l:s1) {
                if (l == '\&quot;') {
                    sgn ^= 1;
                    if (!sgn) v1.emplace_back(tmp), tmp.clear();
                    ss1 += '\&quot;';
                }
                else if (sgn) tmp += l;
                else ss1 += l;
            }
            sgn = 0;
            tmp.clear();
            for (auto &amp;amp;l:s2) {
                if (l == '\&quot;') {
                    sgn ^= 1;
                    if (!sgn) v2.emplace_back(tmp), tmp.clear();
                    ss2 += '\&quot;';
                }
                else if (sgn) tmp += l;
                else ss2 += l;
            }
            if (ss1 != ss2 || v1.size() != v2.size()) cout &amp;lt;&amp;lt; &quot;DIFFERENT\n&quot;;
            else {
                int cnt = 0, vsize = v1.size();
                for (int i = 0; i &amp;lt; vsize; i++) {
                    if (v1[i] != v2[i]) cnt++;
                }
                if (cnt &amp;gt; 1) cout &amp;lt;&amp;lt; &quot;DIFFERENT\n&quot;;
                else cout &amp;lt;&amp;lt; &quot;CLOSE\n&quot;;
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>백준 14970</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2345</guid>
      <comments>https://measurezero.tistory.com/2345#entry2345comment</comments>
      <pubDate>Fri, 14 Mar 2025 10:00:56 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 29279 // C++] Поломка Бамблби</title>
      <link>https://measurezero.tistory.com/2344</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 글쓴이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 29279번 문제인 Поломка&amp;nbsp;Бамблби이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/29279&quot;&gt;https://www.acmicpc.net/problem/29279&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;쿼리로 주어진 범위에 1이 아닌 수가 포함되어 있다면 그 수 하나만 포함된 구간의 mex값은 1이 되고, 따라서 gcd는 항상 1이 된다.&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만 포함되어 있다면 모든 구간의 mex값이 2가 되고, 따라서 gcd는 항상 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;주어진 범위에 1이 아닌 수가 포함되어있는지 여부는 각 배열의 성분을 1인 경우와 아닌 경우 각각 0과 1을 부여한 배열의 누적 합을 계산해 두는 것으로 빠르게 판별할 수 있다.&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;pre id=&quot;code_1741827218422&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
using namespace std;

int N, Q;
int A[100001];

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; N;
    for (int i = 1; i &amp;lt;= N; i++) {
        int x; cin &amp;gt;&amp;gt; x;
        if (x == 1) A[i] = A[i - 1];
        else A[i] = A[i - 1] + 1;
    }
    cin &amp;gt;&amp;gt; Q;
    while (Q--) {
        int L, R; cin &amp;gt;&amp;gt; L &amp;gt;&amp;gt; R;
        if (A[R] - A[L - 1]) cout &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; '\n';
        else cout &amp;lt;&amp;lt; 2 &amp;lt;&amp;lt; '\n';
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 29279</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2344</guid>
      <comments>https://measurezero.tistory.com/2344#entry2344comment</comments>
      <pubDate>Thu, 13 Mar 2025 10:00:06 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 7803 // C++] Burger, French Fries, Soft Drink</title>
      <link>https://measurezero.tistory.com/2343</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 글쓴이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 7803번 문제인 Burger, French Fries, Soft Drink이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/7803&quot;&gt;https://www.acmicpc.net/problem/7803&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;먼저 주어진 B, F, S의 개수가 서로 같지 않다면 당연히 세트를 나누는 것이 불가능하다. 아래에서는 전체 B, F, S의 개수가 서로 같은 상황만을 생각하자.&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;앞의 B, F, S의 개수가 서로 같게 끊을 수 있는 모든 위치를 먼저 구하면, 세트를 나눌 수 있는 지점은 그 부분뿐이며 그 중 \(N-1\)개의 지점을 고르는 것으로 모든 가능한 경우를 찾을 수 있다.&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;아래는 제출한 소스코드이다.&lt;/p&gt;
&lt;pre id=&quot;code_1741738496642&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;string&amp;gt;
using namespace std;

int comb[34][34];
void combinit() {
    comb[0][0] = 1;
    for (int n = 1; n &amp;lt; 34; n++) {
        comb[n][0] = 1;
        for (int r = 1; r &amp;lt; 34; r++) {
            comb[n][r] = comb[n - 1][r - 1] + comb[n - 1][r];
        }
    }
}

int N; string s;
int cnt[128];

void solve() {
    int n = 0;
    cnt['B'] = cnt['F'] = cnt['S'] = 0;
    for (auto &amp;amp;l:s) {
        cnt[l]++;
        if (cnt['B'] == cnt['F'] &amp;amp;&amp;amp; cnt['F'] == cnt['S']) n++;
    }
    if (cnt['B'] != cnt['F'] || cnt['F'] != cnt['S']) {
        cout &amp;lt;&amp;lt; &quot;Impossible\n&quot;;
        return;
    }
    n--;

    if (comb[n][N - 1]) cout &amp;lt;&amp;lt; comb[n][N - 1] &amp;lt;&amp;lt; '\n';
    else cout &amp;lt;&amp;lt; &quot;Impossible\n&quot;;
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    combinit();
    while (cin &amp;gt;&amp;gt; N &amp;gt;&amp;gt; s) solve();
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 7803</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2343</guid>
      <comments>https://measurezero.tistory.com/2343#entry2343comment</comments>
      <pubDate>Wed, 12 Mar 2025 10:00:09 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 30570 // C++] Mini-Tetris 3023</title>
      <link>https://measurezero.tistory.com/2342</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 글쓴이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 7352번 문제인 Mini-Tetris 3023이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/30570&quot;&gt;https://www.acmicpc.net/problem/30570&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;S-tile을 이용하면서 2행의 직사각형 형태를 채우려면 적어도 2개의 Corner 타일이 필요하다는 점을 관찰하자. 또한 2개의 Corner 타일이 있기만 해도 그 사이에 모든 S-tile을 이용할 수 있다는 점을 관찰하자.&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)먼저 S-tile을 사용할 수 있다면 전부 사용하고 (2) 남은 Square 타일과 Corner 타일로 최대한 직사각형을 길게 채우는 것으로 문제를 해결할 수 있다.&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;pre id=&quot;code_1741651409072&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
using namespace std;

int A, B, C, ans;

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; A &amp;gt;&amp;gt; B &amp;gt;&amp;gt; C;
    if (C &amp;gt; 1) {
        ans += B * 2 + 3;
        C -= 2;
    }
    ans += A * 2;
    ans += C / 2 * 3;
    cout &amp;lt;&amp;lt; ans;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 30570</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2342</guid>
      <comments>https://measurezero.tistory.com/2342#entry2342comment</comments>
      <pubDate>Tue, 11 Mar 2025 10:00:44 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 20490 // C++] Рыцарский щит</title>
      <link>https://measurezero.tistory.com/2341</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 글쓴이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 7352번 문제인 Рыцарский&amp;nbsp;щит이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/20490&quot;&gt;https://www.acmicpc.net/problem/20490&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;원하는 길이를 최소화하는 것은 두 삼각형의 맞닿은 길이를 최대화하는 것과 같다는 점을 관찰하자.&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;따라서 문제의 답은 여섯 변의 길이의 합에서 주어진 각각의 두 삼각형의 가장 긴 변의 길이 중 더 작은 값을 두 번 뺀 값이 된다.&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;pre id=&quot;code_1741564962639&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
using namespace std;

int A, B, C, X, Y, Z, P, Q;

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; A &amp;gt;&amp;gt; B &amp;gt;&amp;gt; C &amp;gt;&amp;gt; X &amp;gt;&amp;gt; Y &amp;gt;&amp;gt; Z;
    P = max(A, max(B, C)), Q = max(X, max(Y, Z));
    cout &amp;lt;&amp;lt; A + B + C + X + Y + Z - min(P, Q) * 2;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 20490</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2341</guid>
      <comments>https://measurezero.tistory.com/2341#entry2341comment</comments>
      <pubDate>Mon, 10 Mar 2025 10:00:43 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 7352 // C++] Sorting it All Out</title>
      <link>https://measurezero.tistory.com/2340</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 글쓴이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 7352번 문제인 Sorting it All Out이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/7352&quot;&gt;https://www.acmicpc.net/problem/7352&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;각 수를 정점으로, 대소관계를 방향에지로 모델링하면, 주어진 그래프가 문제의 조건을 만족하는 시점은 그래프가 (1) DAG이고 (2) 최장거리가 \(n\)이 되는 경우이며, 중간에 탐색이 끝나는 시점은 (1) 앞선 조건을 만족하는 순간이 오거나 (2) DAG가 아니게 되는 경우이다.&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;DAG 여부를 알아내는 것은 주어진 그래프에 사이클이 있는지 판정하는 것으로 할 수 있다. 또한 DAG의 최장거리는 위상정렬순으로 정점을 탐색하는 것으로 구할 수 있다. 이 두 알고리즘을 잘 구현하여 문제를 해결하자. 주어질 수 있는 노드의 수가 매우 적으므로, 매 탐색단계마다 위 과정을 매번 수행하는 것으로도 문제를 충분히 해결할 수 있다.&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;한 테스트케이스의 과정 중간에서 DAG가 아니게 되었다는 정보를 알아냈어도 그 테스트케이스에서 주어지는 모든 대소관계를 읽을 필요가 있다는 점에 유의하자.&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;pre id=&quot;code_1741306264884&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;cstring&amp;gt;
#include &amp;lt;queue&amp;gt;
using namespace std;

int N, M;
vector&amp;lt;char&amp;gt; G[128];
pair&amp;lt;char, char&amp;gt; E[1000];

char par[128], lst;
int indeg[128], dist[128];
queue&amp;lt;int&amp;gt; que;

void func() {
    lst = 0;
    memset(par, 0, sizeof(par));
    memset(indeg, 0, sizeof(indeg));
    for (int i = 0; i &amp;lt; N; i++) {
        char x = 'A' + i;
        for (auto &amp;amp;y:G[x]) indeg[y]++;
    }
    memset(dist, 0, sizeof(dist));
    for (int i = 0; i &amp;lt; N; i++) {
        char x = 'A' + i;
        if (!indeg[x]) dist[x] = 1, que.push(x);
    }
    while (!que.empty()) {
        char cur = que.front(); que.pop();
        lst = cur;
        for (auto &amp;amp;nxt:G[cur]) {
            indeg[nxt]--;
            if (!indeg[nxt]) dist[nxt] = dist[cur] + 1, par[nxt] = cur, que.push(nxt);
        }
    }
}

void solve() {
    for (int m = 0; m &amp;lt; M; m++) {
        cin &amp;gt;&amp;gt; E[m].first &amp;gt;&amp;gt; E[m].second &amp;gt;&amp;gt; E[m].second;
    }
    for (char c = 'A'; c &amp;lt;= 'Z'; c++) G[c].clear();
    for (int k = 0; k &amp;lt; M; k++) {
        G[E[k].first].emplace_back(E[k].second);
        func();
        if (dist[lst] == N) {
            cout &amp;lt;&amp;lt; &quot;Sorted sequence determined after &quot; &amp;lt;&amp;lt; k + 1 &amp;lt;&amp;lt; &quot; relations: &quot;;
            string ans = &quot;&quot;;
            while (lst) {
                ans += lst;
                lst = par[lst];
            }
            while (!ans.empty()) {
                cout &amp;lt;&amp;lt; ans.back();
                ans.pop_back();
            }
            cout &amp;lt;&amp;lt; &quot;.\n&quot;;
            return;
        }
        for (int i = 0; i &amp;lt; N; i++) {
            char x = 'A' + i;
            if (indeg[x]) {
                cout &amp;lt;&amp;lt; &quot;Inconsistency found after &quot; &amp;lt;&amp;lt; k + 1 &amp;lt;&amp;lt; &quot; relations.\n&quot;;
                return;
            }
        }
    }
    cout &amp;lt;&amp;lt; &quot;Sorted sequence cannot be determined.\n&quot;;
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; N &amp;gt;&amp;gt; M;
    while (N) {
        solve();
        cin &amp;gt;&amp;gt; N &amp;gt;&amp;gt; M;
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 7352</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2340</guid>
      <comments>https://measurezero.tistory.com/2340#entry2340comment</comments>
      <pubDate>Fri, 7 Mar 2025 10:00:57 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 8100 // C++] Containers</title>
      <link>https://measurezero.tistory.com/2339</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 글쓴이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 8100번 문제인 Containers이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/8100&quot;&gt;https://www.acmicpc.net/problem/8100&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;현재 네 병에 담겨있는 물의 양을 4-tuple로 생각하자. 단, 없는 병의 경우 부피가 0인 병에 0인 물이 채워져있다고 생각하자.&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)를 구하는 문제로 모델링할 수 있다.&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;아래는 제출한 소스코드이다.&lt;/p&gt;
&lt;pre id=&quot;code_1741219303772&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;queue&amp;gt;
using namespace std;

int N;
int A, B, C, D, eA, eB, eC, eD, dist[50][50][50][50];
queue&amp;lt;pair&amp;lt;pair&amp;lt;int, int&amp;gt;, pair&amp;lt;int, int&amp;gt;&amp;gt;&amp;gt; que;

void bfs() {
    dist[A][B][C][D] = 1;
    que.push({{A, B}, {C, D}});
    while (!que.empty()) {
        int a = que.front().first.first, b = que.front().first.second, c = que.front().second.first, d = que.front().second.second; que.pop();
        if (!dist[0][b][c][d]) dist[0][b][c][d] = dist[a][b][c][d] + 1, que.push({{0, b}, {c, d}});
        if (!dist[a][0][c][d]) dist[a][0][c][d] = dist[a][b][c][d] + 1, que.push({{a, 0}, {c, d}});
        if (!dist[a][b][0][d]) dist[a][b][0][d] = dist[a][b][c][d] + 1, que.push({{a, b}, {0, d}});
        if (!dist[a][b][c][0]) dist[a][b][c][0] = dist[a][b][c][d] + 1, que.push({{a, b}, {c, 0}});
        int val;
        val = min(B - b, a);
        if (!dist[a - val][b + val][c][d]) dist[a - val][b + val][c][d] = dist[a][b][c][d] + 1, que.push({{a - val, b + val}, {c, d}});
        val = min(A - a, b);
        if (!dist[a + val][b - val][c][d]) dist[a + val][b - val][c][d] = dist[a][b][c][d] + 1, que.push({{a + val, b - val}, {c, d}});
        val = min(C - c, a);
        if (!dist[a - val][b][c + val][d]) dist[a - val][b][c + val][d] = dist[a][b][c][d] + 1, que.push({{a - val, b}, {c + val, d}});
        val = min(A - a, c);
        if (!dist[a + val][b][c - val][d]) dist[a + val][b][c - val][d] = dist[a][b][c][d] + 1, que.push({{a + val, b}, {c - val, d}});
        val = min(D - d, a);
        if (!dist[a - val][b][c][d + val]) dist[a - val][b][c][d + val] = dist[a][b][c][d] + 1, que.push({{a - val, b}, {c, d + val}});
        val = min(A - a, d);
        if (!dist[a + val][b][c][d - val]) dist[a + val][b][c][d - val] = dist[a][b][c][d] + 1, que.push({{a + val, b}, {c, d - val}});
        val = min(C - c, b);
        if (!dist[a][b - val][c + val][d]) dist[a][b - val][c + val][d] = dist[a][b][c][d] + 1, que.push({{a, b - val}, {c + val, d}});
        val = min(B - b, c);
        if (!dist[a][b + val][c - val][d]) dist[a][b + val][c - val][d] = dist[a][b][c][d] + 1, que.push({{a, b + val}, {c - val, d}});
        val = min(D - d, b);
        if (!dist[a][b - val][c][d + val]) dist[a][b - val][c][d + val] = dist[a][b][c][d] + 1, que.push({{a, b - val}, {c, d + val}});
        val = min(B - b, d);
        if (!dist[a][b + val][c][d - val]) dist[a][b + val][c][d - val] = dist[a][b][c][d] + 1, que.push({{a, b + val}, {c, d - val}});
        val = min(D - d, c);
        if (!dist[a][b][c - val][d + val]) dist[a][b][c - val][d + val] = dist[a][b][c][d] + 1, que.push({{a, b}, {c - val, d + val}});
        val = min(C - c, d);
        if (!dist[a][b][c + val][d - val]) dist[a][b][c + val][d - val] = dist[a][b][c][d] + 1, que.push({{a, b}, {c + val, d - val}});
    }
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; N;
    if (N == 1) cin &amp;gt;&amp;gt; A &amp;gt;&amp;gt; eA;
    else if (N == 2) cin &amp;gt;&amp;gt; A &amp;gt;&amp;gt; B &amp;gt;&amp;gt; eA &amp;gt;&amp;gt; eB;
    else if (N == 3) cin &amp;gt;&amp;gt; A &amp;gt;&amp;gt; B &amp;gt;&amp;gt; C &amp;gt;&amp;gt; eA &amp;gt;&amp;gt; eB &amp;gt;&amp;gt; eC;
    else cin &amp;gt;&amp;gt; A &amp;gt;&amp;gt; B &amp;gt;&amp;gt; C &amp;gt;&amp;gt; D &amp;gt;&amp;gt; eA &amp;gt;&amp;gt; eB &amp;gt;&amp;gt; eC &amp;gt;&amp;gt; eD;
    bfs();

    if (dist[eA][eB][eC][eD]) cout &amp;lt;&amp;lt; dist[eA][eB][eC][eD] - 1;
    else cout &amp;lt;&amp;lt; &quot;NIE&quot;;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 8100</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2339</guid>
      <comments>https://measurezero.tistory.com/2339#entry2339comment</comments>
      <pubDate>Thu, 6 Mar 2025 10:00:02 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 8104 // C++] Fibonacci Words</title>
      <link>https://measurezero.tistory.com/2338</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 글쓴이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 8104번 문제인 Fibonacci Words이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/8104&quot;&gt;https://www.acmicpc.net/problem/8104&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;두 문자열 A와 B에 대하여 A+B에 문자열 S가 포함된 횟수는 (A에 포함된 S의 수) + (B에 포함된 S의 수) + (A와 B에 걸친 S의 수)와 같다는 점을 관찰하자.&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;한편, 문자열의 길이는 한 단계 지날 때마다 기하급수적으로 증가하므로 이를 직접 저장하면 저장해야하는 문자열의 길이가 매우 길어질 것이다. 우리는 다음 단계의 문자열을 구할 때 각각의 문자열의 앞과 뒤의, S의 길이(보다 하나 적은)만큼만의 부분문자열만 알아도 충분하므로 이 점을 이용해 저장할 문자열의 길이를 적절하게 관리해나가자.&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;문제의 답이 64비트 정수 자료형의 범위를 넘을 수 있다는 점에 유의하자. 감이 잘 안 온다면, 찾아야 하는 문자열이 &quot;a&quot;로 주어지면 답이 피보나치 수와 같이 증가함을 관찰하고, 200번째 피보나치 수가 얼마나 큰지 확인해보자. 한편, 수가 매우 커지지는 않으므로 큰 수의 연산을 효율적으로 구현할 필요는 없다.&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;pre id=&quot;code_1741132996324&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
using namespace std;

int N;
string S;
string s[200], dp[200];

void calc(int idx, int val) {
    string A = dp[idx - 2], B = dp[idx - 1];
    while (A.length() &amp;lt; B.length()) A = &quot;0&quot; + A;
    while (A.length() &amp;gt; B.length()) B = &quot;0&quot; + B;
    bool carry = 0;
    int len = A.length();
    for (int i = len - 1; i &amp;gt; -1; i--) {
        int a = A[i] - '0', b = B[i] - '0';
        int x = a + b;
        if (carry) carry = 0, x++;
        if (x &amp;gt; 9) x -= 10, carry = 1;
        A[i] = '0' + x;
    }
    if (carry) A = &quot;1&quot; + A;
    B = to_string(val);
    while (A.length() &amp;lt; B.length()) A = &quot;0&quot; + A;
    while (A.length() &amp;gt; B.length()) B = &quot;0&quot; + B;
    carry = 0;
    len = A.length();
    for (int i = len - 1; i &amp;gt; -1; i--) {
        int a = A[i] - '0', b = B[i] - '0';
        int x = a + b;
        if (carry) carry = 0, x++;
        if (x &amp;gt; 9) x -= 10, carry = 1;
        A[i] = '0' + x;
    }
    if (carry) A = &quot;1&quot; + A;
    dp[idx] = A;
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; S &amp;gt;&amp;gt; N;
    s[0] = &quot;b&quot;, s[1] = &quot;a&quot;;
    if (S == &quot;a&quot;) dp[1] = &quot;1&quot;;
    else dp[1] = &quot;0&quot;;
    if (S == &quot;b&quot;) dp[0] = &quot;1&quot;;
    else dp[0] = &quot;0&quot;;
    for (int i = 2; i &amp;lt; N; i++) {
        string tmp = &quot;&quot;;
        if (s[i - 1].length() &amp;gt;= S.length() - 1) tmp += s[i - 1].substr(s[i - 1].length() - (S.length() - 1), S.length() - 1);
        else tmp += s[i - 1];
        if (s[i - 2].length() &amp;gt;= S.length() - 1) tmp += s[i - 2].substr(0, S.length() - 1);
        else tmp += s[i - 2];
        int tlen = tmp.length(), cnt = 0;
        for (int k = 0; k + S.length() &amp;lt;= tlen; k++) {
            if (tmp.substr(k, S.length()) == S) cnt++;
        }
        calc(i, cnt);
        s[i] = s[i - 1] + s[i - 2];
        if (s[i].length() &amp;gt;= S.length()) s[i] = s[i].substr(0, S.length()) + s[i].substr(s[i].length() - S.length(), S.length());
    }

    cout &amp;lt;&amp;lt; dp[N - 1];
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 8104</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2338</guid>
      <comments>https://measurezero.tistory.com/2338#entry2338comment</comments>
      <pubDate>Wed, 5 Mar 2025 10:00:18 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 33556 // C++] Java String Equals</title>
      <link>https://measurezero.tistory.com/2337</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 글쓴이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 33556번 문제인 Java String Equals이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/33556&quot;&gt;https://www.acmicpc.net/problem/33556&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;주어지는 두 문자열 A와 B는 (1) 문자열 A가 null인 경우, (2) 문자열 A는 null이 아니지만 B는 null인 경우, (3) 문자열 A와 B 모두 null이 아닌 경우의 세 가지 경우로 나눌 수 있다.&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)의 경우 주어지는 두 함수 모두 NullPointerException이 항상 발생함을 지문으로부터 알 수 있다. 또한 (2)의 경우 A와 B가 다름이 명백하므로 두 함수 모두 false를 반환할 것임을 알 수 있다.&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;&quot;null&quot;만이 값이 존재하지 않음을 의미한다는 점에 유의하자. 즉, &quot;Null&quot;, &quot;NULL&quot; 등은 &quot;null&quot;과 같지 않다는 점에 유의하자.&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;pre id=&quot;code_1741046610685&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
using namespace std;

string A, B;

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; A &amp;gt;&amp;gt; B;
    if (A == &quot;null&quot;) cout &amp;lt;&amp;lt; &quot;NullPointerException\nNullPointerException&quot;, exit(0);
    else if (B == &quot;null&quot;) cout &amp;lt;&amp;lt; &quot;false\nfalse&quot;, exit(0);
    
    if (A == B) cout &amp;lt;&amp;lt; &quot;true\n&quot;;
    else cout &amp;lt;&amp;lt; &quot;false\n&quot;;

    for (auto &amp;amp;l:A) l = tolower(l);
    for (auto &amp;amp;l:B) l = tolower(l);
    if (A == B) cout &amp;lt;&amp;lt; &quot;true&quot;;
    else cout &amp;lt;&amp;lt; &quot;false&quot;;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 33556</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2337</guid>
      <comments>https://measurezero.tistory.com/2337#entry2337comment</comments>
      <pubDate>Tue, 4 Mar 2025 10:00:50 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 33510 // C++] 이상한 나누기</title>
      <link>https://measurezero.tistory.com/2336</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 글쓴이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 33510번 문제인 이상한 나누기이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/33510&quot;&gt;https://www.acmicpc.net/problem/33510&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;수가 1이 될 때까지 큰 수 자료형을 이용하여 계산하는 것은 연산에 시간이 오래 걸려 실행시간이 길다.&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;그러나 주어진 수의 각 자리를 저장하고, 직접 위 연산을 받아올림등을 이용하여 손으로 계산하듯이 계산하면 2로 나누는 것은 뒤의 0을 제거하는 것으로, 덧셈은 변하는 자릿수만 변하므로(그리고 그 변화는 각 자리에 대하여 많아야 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;pre id=&quot;code_1740614878856&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;string&amp;gt;
using namespace std;

int ans;
int N; bool carry, chk0;
string s;

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; N &amp;gt;&amp;gt; s;
    for (auto &amp;amp;l:s) {
        if (l == '0') chk0 = 1;
    }
    if (!chk0) {
        if (s == &quot;1&quot;) cout &amp;lt;&amp;lt; 0;
        else cout &amp;lt;&amp;lt; 1;
        exit(0);
    }

    for (int i = N - 1; i &amp;gt; 0; i--) {
        if (carry) {
            if (s[i] == '1') s[i] = '0';
            else s[i] = '1', carry = 0;
        }
        if (s[i] == '1') carry = 1, ans++;
    }
    cout &amp;lt;&amp;lt; ans;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 33510</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2336</guid>
      <comments>https://measurezero.tistory.com/2336#entry2336comment</comments>
      <pubDate>Thu, 27 Feb 2025 10:00:12 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 33541 // C++] 2025는 무엇이 특별할까?</title>
      <link>https://measurezero.tistory.com/2335</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 글쓴이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 33541번 문제인 2025는 무엇이 특별할까?이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/33541&quot;&gt;https://www.acmicpc.net/problem/33541&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;주어진 수를 네 자리를 넘어가지 않을 때까지 1씩 증가시켜나가면서 조건을 만족하는 수를 찾아 문제를 해결하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네 자리 양의 정수는 9000개뿐이므로 이와 같은 풀이법으로도 충분히 빠르게 문제를 해결할 수 있다.&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;다른 방법으로, 조건을 만족하는 정수는 2025, 3025, 9801의 세 가지밖에 없다는 점을 이용하여 조건문만으로도 문제를 해결할 수도 있다.&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;pre id=&quot;code_1740528252035&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
using namespace std;

int N;

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; N;
    if (N &amp;lt; 2025) cout &amp;lt;&amp;lt; 2025;
    else if (N &amp;lt; 3025) cout &amp;lt;&amp;lt; 3025;
    else if (N &amp;lt; 9801) cout &amp;lt;&amp;lt; 9801;
    else cout &amp;lt;&amp;lt; -1;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 33541</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2335</guid>
      <comments>https://measurezero.tistory.com/2335#entry2335comment</comments>
      <pubDate>Wed, 26 Feb 2025 10:00:25 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 33526 // C++] Anti-Fan Death</title>
      <link>https://measurezero.tistory.com/2334</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 글쓴이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 33526번 문제인 Anti-Fan Death이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/33526&quot;&gt;https://www.acmicpc.net/problem/33526&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;\(N\)이 2 이상일 때, 각 문자로 이루어진 \(N\times N\)블록 9개로 구성된 \(3N\times 3N\) 판을 적절히 구성하면 어떤 연속한 세 개의 문자도 같은 두 개의 문자를 포함하며 각 행과 열에 각 문자가 \(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;따라서 \(N\)이 1일 때의 답을 구하고, 나머지 답을 위와 같은 방법으로 구성해 문제를 해결할 수 있다.&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;pre id=&quot;code_1740442823602&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
using namespace std;

int N;
char A[3][3] = {{'Z','N','A'},{'N','A','Z'},{'A','Z','N'}};

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; N;
    for (int r = 0; r &amp;lt; 3; r++) {
        for (int p = 0; p &amp;lt; N; p++) {
            for (int c = 0; c &amp;lt; 3; c++) {
                for (int k = 0; k &amp;lt; N; k++) cout &amp;lt;&amp;lt; A[r][c];
            }
            cout &amp;lt;&amp;lt; '\n';
        }
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 33526</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2334</guid>
      <comments>https://measurezero.tistory.com/2334#entry2334comment</comments>
      <pubDate>Tue, 25 Feb 2025 10:00:33 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 33515 // C++] 노트북 세 대를 가지고 왔다</title>
      <link>https://measurezero.tistory.com/2333</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 글쓴이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 33515번 문제인 노트북 세 대를 가지고 왔다이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/33515&quot;&gt;https://www.acmicpc.net/problem/33515&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;주어지는 두 가지 시간 중 어떤 시간보다도 늦게 형진이가 문제를 해결하면 형진이의 노트북으로 대회를 참여할 수 없다. 그러나 형진이가 두 가지 시간 이하의 시간으로 문제를 해결하면 형진이의 노트북으로 대회를 참여할 수 있다.&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;조건문 또는 min함수를 이용하여 문제를 해결하자.&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;pre id=&quot;code_1740355352802&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
using namespace std;

int x, y;

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; x &amp;gt;&amp;gt; y;
    cout &amp;lt;&amp;lt; min(x, y);
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 33515</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2333</guid>
      <comments>https://measurezero.tistory.com/2333#entry2333comment</comments>
      <pubDate>Mon, 24 Feb 2025 10:00:29 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 33540 // C++] Helping Out</title>
      <link>https://measurezero.tistory.com/2332</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 글쓴이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 33540번 문제인 Helping Out 이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/33540&quot;&gt;https://www.acmicpc.net/problem/33540&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;각 플레이어마다 주어지는 점수의 합을 구하는 문제이다.&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;map 자료구조를 이용하면 문자열에 대응되는 정수를 간단히 저장 및 수정할 수 있다. 또한 (ordered) map은 자료를 오름차순으로 저장하므로 답안을 사전순으로 출력하기에도 매우 유용하다.&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;따라서 map을 이용하면 문제를 간단하게 해결할 수 있다.&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;pre id=&quot;code_1740096144540&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;string&amp;gt;
#include &amp;lt;map&amp;gt;
using namespace std;

int K;
map&amp;lt;string, int&amp;gt; mp;

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; K;
    while (K--) {
        string s; int x; cin &amp;gt;&amp;gt; s &amp;gt;&amp;gt; x;
        mp[s] += x;
    }

    for (auto &amp;amp;p:mp) cout &amp;lt;&amp;lt; p.first &amp;lt;&amp;lt; ' ' &amp;lt;&amp;lt; p.second &amp;lt;&amp;lt; '\n';
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 33540</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2332</guid>
      <comments>https://measurezero.tistory.com/2332#entry2332comment</comments>
      <pubDate>Fri, 21 Feb 2025 10:00:31 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 33531 // C++] Donor Time!</title>
      <link>https://measurezero.tistory.com/2331</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 글쓴이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 33531번 문제인 Donor Time!이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/33531&quot;&gt;https://www.acmicpc.net/problem/33531&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;1부터 \(N\)까지의 번호가 매겨진 정점과 해당 정점들을 잇는 \(S\)개의 양방향 에지가 있을 때 가장 가까운 Donor shop이 어디인지, 그리고 그 거리가 몇인지를 구하는 문제이다. 단, 가장 가까운 Donor Shop이 여럿 존재한다면 번호가 가장 작은 Donor Shop을 찾아야 한다.&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번 정점으로부터 다른 모든 정점까지의 최단거리는 dijkstra 알고리즘을 이용하여 한번에 모두 구할 수 있다. 따라서 이 문제는 dijkstra 알고리즘을 활용하는 것으로 쉽게 해결할 수 있다.&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 이상이므로, dijkstra 과정에서 방문하는 노드의 순서를 1번 정점으로부터 가장 가까운 순으로, 거리가 같다면 번호가 작은 순으로 어렵지 않게 뽑을 수 있어 조건을 만족하는 정점을 찾자마자 해당 답을 출력 후 프로그램을 중간에 종료시켜도 괜찮다.&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;pre id=&quot;code_1740010375002&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;queue&amp;gt;
#include &amp;lt;cstring&amp;gt;
using namespace std;

int N, M;
priority_queue&amp;lt;pair&amp;lt;int, int&amp;gt;, vector&amp;lt;pair&amp;lt;int, int&amp;gt;&amp;gt;, greater&amp;lt;&amp;gt;&amp;gt; pq;
vector&amp;lt;pair&amp;lt;int, int&amp;gt;&amp;gt; G[10001];
bool donor[10001];
int dist[10001];
void dijkstra() {
    pq.push(make_pair(0, 1));
    memset(dist, 0x3f, sizeof(dist));
    dist[0] = 0;
    while (!pq.empty()) {
        int cur = pq.top().second, d = pq.top().first; pq.pop();
        if (dist[cur] &amp;lt; d) continue;
        if (donor[cur]) {
            cout &amp;lt;&amp;lt; cur &amp;lt;&amp;lt; ' ' &amp;lt;&amp;lt; d;
            exit(0);
        }
        for (auto &amp;amp;p:G[cur]) {
            int nxt = p.first, dd = d + p.second;
            if (dd &amp;lt; dist[nxt]) dist[nxt] = dd, pq.push(make_pair(dd, nxt));
        }
    }
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; N &amp;gt;&amp;gt; M;
    while (M--) {
        int x, y, w; cin &amp;gt;&amp;gt; x &amp;gt;&amp;gt; y &amp;gt;&amp;gt; w;
        G[x].emplace_back(make_pair(y, w));
        G[y].emplace_back(make_pair(x, w));
    }
    cin &amp;gt;&amp;gt; M;
    while (M--) {
        int x; cin &amp;gt;&amp;gt; x;
        donor[x] = 1;
    }
    dijkstra();
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 33531</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2331</guid>
      <comments>https://measurezero.tistory.com/2331#entry2331comment</comments>
      <pubDate>Thu, 20 Feb 2025 10:00:16 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 33532 // C++] Efficient Printing</title>
      <link>https://measurezero.tistory.com/2330</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 글쓴이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 33532번 문제인 Efficient Printing이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/33532&quot;&gt;https://www.acmicpc.net/problem/33532&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;어떤 양의 정수의 10진수 표기의 0의 개수는 이 정수가 10으로 몇 번 나누어떨어지는지를 말해준다. 10으로 나누어떨어지는 횟수는 이 정수가 2와 5를 소인수로 몇 개씩 가지고 있는지를 구해 둘 중 작은 값을 찾는 것으로 구할 수 있다.&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!\)의 경우 소인수 2의 개수가 5의 개수보다 항상 많거나 같으므로, 문제에서 구하고자 하는 값은 소인수 5의 개수를 구하는 것으로 알 수 있다.&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의 1승, 2승, 3승... 의 배수가 \(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;아래는 제출한 소스코드이다.&lt;/p&gt;
&lt;pre id=&quot;code_1739923406499&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
using namespace std;
typedef long long ll;

ll N, K = 5, ans;

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; N;
    while (K &amp;lt;= N) ans += N / K, K *= 5;
    cout &amp;lt;&amp;lt; ans;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 33532</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2330</guid>
      <comments>https://measurezero.tistory.com/2330#entry2330comment</comments>
      <pubDate>Wed, 19 Feb 2025 10:00:34 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 33488 // C++] 아름다운 수열</title>
      <link>https://measurezero.tistory.com/2329</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 글쓴이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 33488번 문제인 아름다운 수열이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/33488&quot;&gt;https://www.acmicpc.net/problem/33488&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;\(|i-j|\)가 소수이면 \(|a_i-a_j|\)도 소수임을 만족하는 수열 \(a_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;\(|i-j|\)와 \(|a_i-a_j|\) 두 식의 구조가 비슷하다는 점을 눈여겨보자. 이를 파고들면 \(a_k=k\)와 같은 수열의 경우 \(|a_i-a_j|=|i-j|\)이므로 위의 조건을 항상 만족함을 관찰할 수 있다.&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;pre id=&quot;code_1739837089930&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
using namespace std;

int T, N;

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; T;
    while (T--) {
        cin &amp;gt;&amp;gt; N;
        cout &amp;lt;&amp;lt; &quot;YES\n&quot;;
        for (int i = 1; i &amp;lt;= N; i++) cout &amp;lt;&amp;lt; i &amp;lt;&amp;lt; ' ';
        cout &amp;lt;&amp;lt; '\n';
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 33488</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2329</guid>
      <comments>https://measurezero.tistory.com/2329#entry2329comment</comments>
      <pubDate>Tue, 18 Feb 2025 10:00:49 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 5714 // C++] Isosceles Triangles</title>
      <link>https://measurezero.tistory.com/2328</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 글쓴이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 5714번 문제인 Isoceles Triangles이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/5714&quot;&gt;https://www.acmicpc.net/problem/5714&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;세 점이 모두 정수점인 정삼각형은 존재하지 않으므로, 각각의 이등변삼각형은 길이가 같은 두 변에 공통으로 포함될 꼭짓점을 먼저 정하고 그 점으로부터 거리가 같은 두 점을 고르는 식으로 모두 찾을 수 있다는 점을 관찰하자.&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;아래는 제출한 소스코드이다.&lt;/p&gt;
&lt;pre id=&quot;code_1739750611423&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;algorithm&amp;gt;
using namespace std;
typedef long long ll;

ll N;
ll P[1000][2], A[1000];

void solve() {
    ll ans = 0;
    for (int i = 0; i &amp;lt; N; i++) cin &amp;gt;&amp;gt; P[i][0] &amp;gt;&amp;gt; P[i][1];
    for (int i = 0; i &amp;lt; N; i++) {
        for (int j = 0; j &amp;lt; N; j++) {
            ll dx = P[i][0] - P[j][0], dy = P[i][1] - P[j][1];
            A[j] = dx * dx + dy * dy;
        }
        sort(A, A + N);
        ll old = -1, combo = 0;
        for (int j = 0; j &amp;lt; N; j++) {
            if (A[j] == old) combo++;
            else old = A[j], ans += combo * (combo - 1) / 2, combo = 1;
        }
        ans += combo * (combo - 1) / 2;
    }
    cout &amp;lt;&amp;lt; ans &amp;lt;&amp;lt; '\n';
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; N;
    while (N) solve(), cin &amp;gt;&amp;gt; N;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 5714</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2328</guid>
      <comments>https://measurezero.tistory.com/2328#entry2328comment</comments>
      <pubDate>Mon, 17 Feb 2025 10:00:41 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 21430 // C++] Свечки</title>
      <link>https://measurezero.tistory.com/2327</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 글쓴이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 21430번 문제인 Свечки이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/21430&quot;&gt;https://www.acmicpc.net/problem/21430&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;어떤 직선과 그 직선 위에 놓여있지 않은 점에 대하여, 점이 직선의 어느 쪽에 있느냐에 따라 \(ax+by+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;이를 이용하면, \(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;각 직선을 비트로 저장하는 대신 trie 자료구조를 활용해도 좋다.&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;pre id=&quot;code_1739491906859&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;bitset&amp;gt;
#include &amp;lt;algorithm&amp;gt;
using namespace std;

int N, M, R;
int P[10000][2];
vector&amp;lt;bool&amp;gt; st[10000];

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; N &amp;gt;&amp;gt; M &amp;gt;&amp;gt; R;
    for (int i = 0; i &amp;lt; N; i++) cin &amp;gt;&amp;gt; P[i][0] &amp;gt;&amp;gt; P[i][1];
    for (int i = 0; i &amp;lt; N; i++) st[i].resize(M);
    for (int j = 0; j &amp;lt; M; j++) {
        int A, B, C; cin &amp;gt;&amp;gt; A &amp;gt;&amp;gt; B &amp;gt;&amp;gt; C;
        for (int i = 0; i &amp;lt; N; i++) {
            if (A * P[i][0] + B * P[i][1] + C &amp;gt; 0) st[i][j] = 1;
        }
    }
    sort(st, st + N);
    for (int i = 0; i + 1 &amp;lt; N; i++) {
        if (st[i] == st[i + 1]) {
            cout &amp;lt;&amp;lt; &quot;YES&quot;;
            return 0;
        }
    }
    
    cout &amp;lt;&amp;lt; &quot;NO&quot;;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 21430</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2327</guid>
      <comments>https://measurezero.tistory.com/2327#entry2327comment</comments>
      <pubDate>Fri, 14 Feb 2025 10:00:03 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 27222 // C++] Штангист</title>
      <link>https://measurezero.tistory.com/2326</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;※ 글쓴이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 볼&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 27222번 문제인 Штангист이다.&lt;br /&gt;문제는 아래 링크를 확인하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/27222&quot;&gt;https://www.acmicpc.net/problem/27222&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;각각의 날에 대하여 그 날 훈련을 했는지, 했다면 체중이 증가했는지를 확인해 그 증가량을 모두 합하는 것으로 답을 구할 수 있다.&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;아래는 제출한 소스코드이다.&lt;/p&gt;
&lt;pre id=&quot;code_1739404888943&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
using namespace std;

int N, ans;
bool A[1000];

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin &amp;gt;&amp;gt; N;
    for (int i = 0; i &amp;lt; N; i++) cin &amp;gt;&amp;gt; A[i];
    for (int i = 0; i &amp;lt; N; i++) {
        int L, R; cin &amp;gt;&amp;gt; L &amp;gt;&amp;gt; R;
        if (A[i]) ans += max(R - L, 0);
    }
    cout &amp;lt;&amp;lt; ans;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BOJ</category>
      <category>백준 27222</category>
      <author>measurezero</author>
      <guid isPermaLink="true">https://measurezero.tistory.com/2326</guid>
      <comments>https://measurezero.tistory.com/2326#entry2326comment</comments>
      <pubDate>Thu, 13 Feb 2025 10:00:33 +0900</pubDate>
    </item>
  </channel>
</rss>