<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>mo1lusca의 블로그</title>
    <link>https://mollusca0907.tistory.com/</link>
    <description>안녕하세요..</description>
    <language>ko</language>
    <pubDate>Fri, 22 May 2026 08:26:23 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>mo1lusca</managingEditor>
    <image>
      <title>mo1lusca의 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/7885326/attach/7793e7f3dd1e4627b896178aa66a9657</url>
      <link>https://mollusca0907.tistory.com</link>
    </image>
    <item>
      <title>GoodBye BOJ</title>
      <link>https://mollusca0907.tistory.com/76</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요 4개월 만에 돌아온 mollusca입니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;제 블로그를 보는사람이 얼마나 있을진 모르겠지만 일단 4개월간 뭐 했는지 근황부터 간단하게 써보겠습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1632&quot; data-origin-height=&quot;551&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Kcp1Y/dJMcacCWA72/fJX8ukYPxWXx5XCCg0Xybk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Kcp1Y/dJMcacCWA72/fJX8ukYPxWXx5XCCg0Xybk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Kcp1Y/dJMcacCWA72/fJX8ukYPxWXx5XCCg0Xybk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKcp1Y%2FdJMcacCWA72%2FfJX8ukYPxWXx5XCCg0Xybk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1632&quot; height=&quot;551&quot; data-origin-width=&quot;1632&quot; data-origin-height=&quot;551&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;다이아를 찍어버렸습니다.. 네....&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;사실 실력에 비하면 많이 과분한 티어라고 생각합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이전엔 세그트리를 주구장창 먹으면서 플레까지 날먹했었는데&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이번 다이아 도달의 일등공신은 기하학입니다. 재밌는 문제가 많더라구요.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; 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;style3&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;540&quot; data-origin-height=&quot;258&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/magm6/dJMcajvf9IW/MaGyiOFjdNmZCTJaYuvefk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/magm6/dJMcajvf9IW/MaGyiOFjdNmZCTJaYuvefk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/magm6/dJMcajvf9IW/MaGyiOFjdNmZCTJaYuvefk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmagm6%2FdJMcajvf9IW%2FMaGyiOFjdNmZCTJaYuvefk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;540&quot; height=&quot;258&quot; data-origin-width=&quot;540&quot; data-origin-height=&quot;258&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;겨울방학 기간에는 처참한 스트릭을 볼 수 있었습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;변명을 해보자면 이제 고2인 관계로 학업에 정진하기 위해 수학학원을 매일매일 갔습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;작년 여름부터 잇던 스트릭이 끊기고 ps 흥미가 살짝 사라졌던 것 같기도 합니다. &lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;s&gt;권태기&lt;/s&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그러다 3월에 개학하고 나서부터 다시 백준을 좀 풀었었습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;친구들이랑 &quot;천천히 꾸준히&quot; 라는 이름의 ps 스터디를 한 덕이 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;4월부터는 중간고사 준비를 위해 스터디는 3월까지만 했습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;백준 섭종 공지가 떴습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;사실 요즘에는 백준 말고 코포나 앳코더를 좀 치기 시작해서 백준도 슬슬 떠나게 될 거 같다고 생각했습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;근데 그게 이런 형태로 나타날 줄은 몰랐습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;곧바로 수긍한 것은 아니지만 내부사정이 있었겠거니 했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;그래서 뭐 다른 oj를 좀 찾아보다가 2주가 지나고 이 글을 쓰는 지금 백준은 섭종된상태입니다 흑흑&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;2주간 좀 많은 에피소드가 있었는데 말안할겁니다. 너무구질구질합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;섭종공지가 뜬 후 2주간, 어떻게 할지 생각을 해봤습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;당장 제가 이주할 oj뿐이 아니라 저에게는 책임져야 할 동아리 부원들이 있기 때문입니다. ㅠㅠ&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;결론은 JUNGOL로 갈것입니다. (동아리)&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;저의 경우에는 그냥 ps를 접는 방향으로 정했습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;사실 qoj같은곳으로 가도 됐었을 거 같지만 애초에 요즘 ps에 대한 흥미가 떨어져 버린 것 같습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;학업도 학업이다보니 선택과 집중을 해야 할 시기인 것 같습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그래도 정 하고싶다면 코포나 앳코더같은 cp 위주로 할 것 같습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;작년 이맘때에 전공 동아리 입부를 계기로 백준을 처음 접했었는데&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;진짜 제대로 시작한지 1년만에 이렇게 되다니 안타깝습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;저도 저지만 저보다 백준과 오래 함께한 주변 친구들은 어떨지 모르겠습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;돌이켜보면 백준덕분에 정말 알찬 1년이었던 것 같습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 블로그를 보는 사람이 진정 몇이나 될까 다시한번 생각해봅니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;아마 제 학교친구들 몇명뿐이 볼 것 같습니다. 아무도 안볼수도&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그치만 간간히 글은 올리겠습니다. 일기가 올라올 수도 있습니다.(!)&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;봐주시는 모든 분들께 감사하무니다&lt;/p&gt;</description>
      <category>개인저장용</category>
      <author>mo1lusca</author>
      <guid isPermaLink="true">https://mollusca0907.tistory.com/76</guid>
      <comments>https://mollusca0907.tistory.com/76#entry76comment</comments>
      <pubDate>Wed, 29 Apr 2026 00:28:28 +0900</pubDate>
    </item>
    <item>
      <title>[백준] 4008 특공대 - C++</title>
      <link>https://mollusca0907.tistory.com/75</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/4008&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/4008&lt;/a&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;CHT의 바이블&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;점화식은 $DP[i] = max(\;a(S[i]-S[j])^{2}+b(S[i]-S[j] ) +c+DP[j]\;)$ 이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;$j$와 관련 없는 항은 $max$밖으로 빼줄 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;$(S[i]-S[j])^{2}$를 전개한 뒤 &amp;amp;max&amp;amp; 밖으로 항을 빼주자.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그럼 $DP[i] = max(\;-2aS[i]S[j] + aS[j]^{2} - bS[j] + DP[j]\;) + aS[i]^{2} + bS[i] + c$이 되고,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;$max$ 밖의 항들은 상수항이므로 나중에 더해줘도 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;$max$ 안의 식은 $S[i]$에 대한 일차식으로 나타낼 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 $DP[i] = max(\;-2aS[j]x + (aS[j]^{2}-bS[j]+DP[j])\;) + Constant$ 이고,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;일차식의 기울기는 $-2aS[j]$, y절편은 $aS[j]^{2}-bS[j]+DP[j]$임을 알 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이를 이용해 CHT를 짜주자.&lt;/p&gt;
&lt;pre id=&quot;code_1766541233496&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;bits/stdc++.h&amp;gt;
using namespace std;
using ll = long long;
using db = double;
const int MAX = 1'000'005;

struct Line {
	ll m, b; //기울기, y절편
	db start; //스타트포인트 (반직선형태)
};

ll a, b, c;
vector&amp;lt;ll&amp;gt;s(MAX), dp(MAX);
vector&amp;lt;Line&amp;gt;hull;

db cross(Line&amp;amp; a, Line&amp;amp; b) { //두 직선의 교점의 x좌표를 구하는 함수
	return (db)(a.b - b.b) / (db)(b.m - a.m);
}
ll constant(ll i) {
	return a * s[i] * s[i] + b * s[i] + c;
}
ll m(ll j) {
	return 1LL * -2 * a * s[j];
}
ll k(ll j) {
	return (1LL * a * s[j] * s[j]) - (1LL * b * s[j]) + (1LL * dp[j]);
}

int main() {
	int n;
	cin &amp;gt;&amp;gt; n;

	cin &amp;gt;&amp;gt; a &amp;gt;&amp;gt; b &amp;gt;&amp;gt; c;
	for (int i = 1; i &amp;lt;= n; i++) {
		ll x;
		cin &amp;gt;&amp;gt; x;
		s[i] = s[i - 1] + x;
	}

	int idx = 0;

	for (int i = 1; i &amp;lt;= n; i++) {
		dp[i] = constant(i); //상수항

		if (!hull.empty()) {
			while (idx + 1 &amp;lt; hull.size() &amp;amp;&amp;amp; hull[idx + 1].start &amp;lt; s[i]) {
				idx++;
			}
			dp[i] = max(dp[i], hull[idx].m * s[i] + hull[idx].b + constant(i));
		}
		Line l = { m(i), k(i), 0 };

		while (!hull.empty()) {
			l.start = cross(hull.back(), l);
			if (hull.back().start &amp;lt; l.start) {
				break;
			}
			hull.pop_back();
		}
		hull.push_back(l);
	}
	cout &amp;lt;&amp;lt; dp[n];
	return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;idx는 j의 역할을 해준다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; 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;</description>
      <category>PS</category>
      <category>C++</category>
      <category>PS</category>
      <category>백준</category>
      <author>mo1lusca</author>
      <guid isPermaLink="true">https://mollusca0907.tistory.com/75</guid>
      <comments>https://mollusca0907.tistory.com/75#entry75comment</comments>
      <pubDate>Wed, 24 Dec 2025 11:01:43 +0900</pubDate>
    </item>
    <item>
      <title>[백준] 13263 나무 자르기 - C++</title>
      <link>https://mollusca0907.tistory.com/74</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/13263&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/13263&lt;/a&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;$DP[i]$ : $i$번째 나무를 높이가 1이 되게 하는 최소비용 이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그럼 점화식이 $DP[i] = min( A[i] * B[j] + DP[j] )\;(0\le j &amp;lt; i )$ 임을 알 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이때 $A[i] * B[j]$는 $i$보다 작은 $j$에 대해, $i$번째 나무를 $j$번째 비용으로 모두 자른다는 뜻이고,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;$DP[j]$는 $j$번째 비용을 사용하기 위해 $j$번째 나무를 자르는걸 의미한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 n이 100,000이라 $O(n^{2})$은 안되기 때문에....&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;CHT를 써줘야 한다!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;문제 조건에 의해 $a_{i}$는 단조증가하고, $b_{i}$는 단조감소하기 때문에, CHT를 쓸 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1766113880172&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;bits/stdc++.h&amp;gt;
using namespace std;
using ll = long long;
using db = double;

struct Line {
	ll m, b; //기울기, y절편
	db start; //스타트포인트 (반직선형태)
};

db cross(Line&amp;amp; a, Line&amp;amp; b) { //두 직선의 교점의 x좌표를 구하는 함수
	return (db)(a.b - b.b) / (db)(b.m - a.m);
}

int main() {
	int n;
	cin &amp;gt;&amp;gt; n;
	vector&amp;lt;ll&amp;gt; va(n), vb(n), dp(n);
	for (int i = 0; i &amp;lt; n; i++) {
		cin &amp;gt;&amp;gt; va[i];
	}
	for (int i = 0; i &amp;lt; n; i++) {
		cin &amp;gt;&amp;gt; vb[i];
	}
	vector&amp;lt;Line&amp;gt;hull; //직선을 담을 컨테이너
    
	for (int i = 1; i &amp;lt; n; i++) {
    
		Line l = { vb[i - 1], dp[i - 1], 0 }; //새로운 직선 정의
		while (!hull.empty()) { //가장 최근 직선과의 교점을 보고, 새로운 직선에 의해 쓸모없어지는 기존 직선을 삭제.
			l.start = cross(hull.back(), l);
			if (hull.back().start &amp;lt; l.start) {
				break;
			}
			hull.pop_back();
		}
		hull.push_back(l);

		ll x = va[i]; //함숫값을 구하려는 x좌표

		//이분탐색을 통해 함숫값을 구하려는 x좌표가 포함된 구간을 담당하는 직선을 찾음
		int pos = hull.size() - 1; //res
		if (x &amp;lt; hull.back().start) {
			int left = 0;
			int right = hull.size() - 1;
			while (left + 1 &amp;lt; right) {
				int mid = (left + right) / 2;
				if (x &amp;lt; hull[mid].start) {
					right = mid;
				}
				else {
					left = mid;
				}
			}
			pos = left;
		}
		dp[i] = hull[pos].m * x + hull[pos].b;
	}
	cout &amp;lt;&amp;lt; dp[n-1];
	return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;설명은 주석에 달아두었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;생각보다는 직관적인 것 같다..&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blog.naver.com/kks227/221418495037&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://blog.naver.com/kks227/221418495037&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; 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;</description>
      <category>PS</category>
      <category>C++</category>
      <category>PS</category>
      <category>백준</category>
      <author>mo1lusca</author>
      <guid isPermaLink="true">https://mollusca0907.tistory.com/74</guid>
      <comments>https://mollusca0907.tistory.com/74#entry74comment</comments>
      <pubDate>Fri, 19 Dec 2025 12:14:00 +0900</pubDate>
    </item>
    <item>
      <title>[Unifox] 동아리 프로젝트  - 트리에서의 구간 쿼리 알고리즘 시각화</title>
      <link>https://mollusca0907.tistory.com/73</link>
      <description>&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;목차&amp;nbsp;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;주제 선정 동기&lt;/li&gt;
&lt;li&gt;개념 설명
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;선형 구조에서의 구간 쿼리&lt;/li&gt;
&lt;li&gt;비선형 구조에서의 구간 쿼리&lt;/li&gt;
&lt;li&gt;ETT, HLD&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;주요 코드 설명&lt;/li&gt;
&lt;li&gt;배운점, 느낀점 및 개선점&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 주제 선정 동기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동아리에서 처음 트리 자료구조에 대해 배우고 흥미가 생겨, 여름방학과 2학기 중 개인적으로 트리에 대해 공부했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트리에서의 구간 쿼리와 관련한 알고리즘도 공부했는데, 아이디어가 상당히 재밌고 시각화하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이해하기도 훨씬 쉬울 것 같아 주제로 선정하게 되었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 개념 설명&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;i. 선형 구조에서의 구간 쿼리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구간 쿼리란, 자료구조 내의 연속된 범위에 대해 업데이트/질의(쿼리)를 수행하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 설명해 보겠다.. { 1, 3, 2, 4, 5 } 라는 배열이 있을 때, [1,3] 구간의 합을 구하는 쿼리를 수행하면, 3+2+4=9라는 결과를 반환하고... 대충 이런 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방금 말했듯 구간 쿼리는 당연하게도 구간에 대해 쿼리를 수행하기 때문에, 연속된 범위로 표현되는 구간이 존재해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 성질 때문에 대부분의 구간 쿼리는 선형구조 (배열, 리스트 등)에서 수행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선형 구조에서 구간 쿼리를 수행하는 대표적인 알고리즘으로는.. 누적합, 세그먼트 트리, Mo'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;선형 구조(배열)의 각 원소들을 리프 노드로 가지며, 내부 노드는 자신의 두 자식의 값을 연산한 결과를 값으로 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 1, 4라는 값을 가진 연속된 두 리프 노드가 있고, 구간합을 구하려고 한다면, 이 두 리프 노드를 동시에 자식으로 가지는 부모 노드에는 5(=1+4)라는 값이 들어간다.&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;ii. 비선형 구조에서의 구간 쿼리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 비선형 구조에서는 어떻게 구간 쿼리를 수행할 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 간단히 생각해 보면 그래프에서는 사이클이 있기 때문에 구간을 잘 정의 할 수 없다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러므로 여기서 말하는 비선형 구조는 결국 트리라고 생각해도 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 본론으로 돌아와서, 구간 쿼리를 수행하기 전에 트리에서의 구간이 무엇인가 알 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 생각해보면 트리에서의 구간의 기준은 크게 두 가지로 나눠짐을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째는 임의의 노드 u, v에 대한 경로이다. 트리에서는 임의의 한 정점에서 다른 한 정점까지의 경로가 유일하므로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(u, v) 경로는 구간이라고 생각할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째는 임의의 노드 u를 루트 노드로 삼는 서브트리이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이런 기준을 가지고 구간 쿼리를 어떻게 수행하라는 것인가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매우 직관적으로 생각해서, 트리를 선형구조로 쫙 펴버리면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, 트리에서의 경로를 구간으로 나타내는 알고리즘이 HLD, 서브트리를 구간으로 나타내는 알고리즘이 ETT이다.&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;iii. ETT, HLD&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ETT란, Euler Tour Technique의 약자로, DFS Ordering이라고도 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ETT는 트리에서의 임의의 서브트리를 연속된 구간으로 나타낼 수 있게 해 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구체적으로는, DFS를 수행하며 각 노드별로 in, out값을 가지게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;in은 DFS를 수행하며 해당 노드를 처음으로 방문한 시점이고, out은 해당 노드의 모든 자손 노드가 탐색이 완료된 시점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 in, out값을 잡으면 임의의 정점 u에 대해, u를 루트로 하는 서브트리의 모든 정점은 [ in[u], out[u] ] 내의 구간으로 표현된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HLD란, Heavy-Light Decomposition의 약자이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 아이디어는, 비선형 구조(트리)를 여러 개의 선형 구조로 분해하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 서브트리의 크기를 기준으로 간선을 나눈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확히는, 부모 노드 u에서 자식 노드 v로 가는 간선 (u, v)가 있을 때, v의 서브트리의 크기가 u의 서브트리의 크기의 절반 이상인 경우 간선 (u, v)를 heavy edge라고 하고 노드 v를 heavy child 내지는 heavy node라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 heavy edge가 아닌 u와 연결된 나머지 간선은 light edge라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 식으로 heavy edge를 정의하면, 임의의 노드에서 자식으로 향하는 heavy edge는 최대 한 개 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 트리 내의 모든 노드가 heavy chain들 중 하나에는 속한다는 것을 알 수 있다. (자기 자신이 체인인 경우도 포함한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 임의의 노드에서 자식으로 향하는 heavy edge를 계속 따라간다고 해보자. 앞서 말한 특성 때문에 heavy edge로 이루어진 하나의 체인(chain)이 나오게 되고, 이를 heavy chain이라 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;heavy chain은 선형구조이고, 트리 내의 모든 경로는 각기 다른 부분 heavy chain의 집합으로 표현할 수 있으므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트리 내의 모든 경로에 대해 선형구조처럼 구간 쿼리를 날릴 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말하자면, 트리를 적당히 잘라서 선형으로 만든 다음 연산한다는 것이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 주요 코드 설명&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;tree.py&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세그먼트 트리, HLD, ETT를 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;pre id=&quot;code_1764957650616&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class SegTree:
    def __init__(self, n):
        self.n = n
        self.tree = [0] * (4 * n + 16)
        self.lazy = [0] * (4 * n + 16)
    
    def push(self, idx, left, right):
        if self.lazy[idx] != 0:
            self.tree[idx] += self.lazy[idx] * (right - left + 1)
            if left != right:
                self.lazy[idx*2] += self.lazy[idx]
                self.lazy[idx*2 + 1] += self.lazy[idx]
            self.lazy[idx] = 0

    def update(self, idx, left, right, cleft, cright, cval):
        self.push(idx, left, right)
        if cright &amp;lt; left or right &amp;lt; cleft:
            return
        if cleft &amp;lt;= left and right &amp;lt;= cright:
            self.lazy[idx] += cval
            self.push(idx, left, right)
            return
        mid = (left + right) // 2
        self.update(idx*2, left, mid, cleft, cright, cval)
        self.update(idx*2+1, mid+1, right, cleft, cright, cval)
        self.push(idx*2, left, mid)
        self.push(idx*2+1, mid+1, right)
        self.tree[idx] = self.tree[idx*2] + self.tree[idx*2+1]
        
    def query(self, idx, left, right, qleft, qright):
        self.push(idx, left, right)
        if qright &amp;lt; left or right &amp;lt; qleft:
            return 0
        if qleft &amp;lt;= left and right &amp;lt;= qright:
            return self.tree[idx]
        mid = (left + right) // 2
        return self.query(idx*2, left, mid, qleft, qright) + self.query(idx*2+1, mid+1, right, qleft, qright)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구간 업데이트 &amp;amp; 구간 쿼리 합연산 세그트리를 구현했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명색이 시각화(시뮬레이터)인데 점 업데이트면 너무 없어보여서... lazy하게 갱신해주자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;pre id=&quot;code_1764957792830&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class HLD:
    # HLD용 클래스
    def __init__(self, n):
        # HLD 구현용
        self.n = n
        self.size = [0] * (n + 1)        # 서브트리 크기
        self.dep = [0] * (n + 1)         # 깊이
        self.par = [0] * (n + 1)         # 부모 노드
        self.top = [0] * (n + 1)         # 체인의 최상단 정점
        self.in_time = [0] * (n + 1)     # ETT 시작 시간
        self.out_time = [0] * (n + 1)    # ETT 종료 시간
        self.g = [[] for _ in range(n + 1)]  # 그래프
        self.cnt = 0                     # ETT용
        self.seg = SegTree(n)
        
        # 시각화용
        self.id = [0] * (n + 1)          # 체인 ID
        self.chain_cnt = 0               # 체인 개수
        self.val = [0] * (n + 1)         # 노드 값
    
    def add_edge(self, u, v):
        # 간선 추가
        self.g[u].append(v)
        self.g[v].append(u)
    
    def set_value(self, v, value):
        # main에서 이 함수를 통해 값을 넘겨줄 것임
        self.val[v] = value
    
    def dfs1(self, v=1, p=0):
        # par, size, dep 저장, heavy 간선 앞으로 보내기
        self.par[v] = p
        self.size[v] = 1
        for i in range(len(self.g[v])):
            child = self.g[v][i]
            if child == p:
                continue
            self.dep[child] = self.dep[v] + 1
            self.par[child] = v
            self.dfs1(child, v)
            self.size[v] += self.size[child]
            if self.g[v][0] == p or self.size[child] &amp;gt; self.size[self.g[v][0]]:
                self.g[v][0], self.g[v][i] = self.g[v][i], self.g[v][0]
    
    def dfs2(self, v=1, p=0):
        # dfs ordering
        self.cnt += 1
        self.in_time[v] = self.cnt
        
        # 체인 ID 부여
        if v == self.top[v]:
            self.chain_cnt += 1
        self.id[v] = self.chain_cnt
        
        for child in self.g[v]:
            if child == p:
                continue
            # Heavy edge면 같은 체인, 아니면 새로운 체인 시작
            self.top[child] = self.top[v] if child == self.g[v][0] else child
            self.dfs2(child, v)
        self.out_time[v] = self.cnt
    
    def init(self):
        # HLD 초기화
        self.dep[1] = 0
        self.par[1] = 0
        self.top[1] = 1
        self.dfs1()
        self.dfs2()
        
        # 입력받은 값으로 세그트리 리프노드 채우기
        for v in range(1, self.n + 1):
            self.seg.update(1,1,self.n,self.in_time[v],self.in_time[v], self.val[v])&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HLD 사전 설정? 함수들이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 HLD 구현 코드와 딱히 다를점은 없고, set_value 함수를 통해 main.py에서 입력을 받는 정도만 기억하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;pre id=&quot;code_1764963154979&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    def update(self, u, v, val):
        affected_nodes = []
        
        while self.top[u] != self.top[v]:
            if self.dep[self.top[u]] &amp;lt; self.dep[self.top[v]]:
                u, v = v, u
            head = self.top[u]
            
            for node in range(1,self.n+1):
                if self.in_time[head] &amp;lt;= self.in_time[node] &amp;lt;= self.in_time[u]:
                    affected_nodes += [node]
                    self.val[node] += val
                
            yield (affected_nodes.copy(), 'UPDATE', val)
            self.seg.update(1, 1, self.n, self.in_time[head], self.in_time[u], val)
            u = self.par[head]
        
        if self.dep[u] &amp;gt; self.dep[v]:
            u, v = v, u
        
        for node in range(1,self.n+1):
            if self.in_time[u] &amp;lt;= self.in_time[node] &amp;lt;= self.in_time[v]:
                affected_nodes += [node]
                self.val[node] += val

        yield (affected_nodes.copy(), 'UPDATE', val)
        self.seg.update(1, 1, self.n, self.in_time[u], self.in_time[v], val)
    
    def query(self, u, v):
        affected_nodes = []
        res = 0
        
        while self.top[u] != self.top[v]:
            if self.dep[self.top[u]] &amp;lt; self.dep[self.top[v]]:
                u, v = v, u
            head = self.top[u]
            
            for node in range(1,self.n+1):
                if self.in_time[head] &amp;lt;= self.in_time[node] &amp;lt;= self.in_time[u]:
                    affected_nodes += [node]
            
            res += self.seg.query(1, 1, self.n, self.in_time[head], self.in_time[u])
            yield (affected_nodes.copy(), 'QUERY', res)
            u = self.par[head]
        
        if self.dep[u] &amp;gt; self.dep[v]:
            u, v = v, u
        
        for node in range(1,self.n+1):
            if self.in_time[u] &amp;lt;= self.in_time[node] &amp;lt;= self.in_time[v]:
                affected_nodes += [node]

        res += self.seg.query(1, 1, self.n, self.in_time[u], self.in_time[v])
        yield (affected_nodes.copy(), 'QUERY', res)
    
    def is_heavy(self, u, v):
        # heavy child인지 검사
        if self.dep[u] &amp;gt; self.dep[v]:
            u, v = v, u
        return self.par[v] == u and len(self.g[u]) &amp;gt; 0 and self.g[u][0] == v&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;chain들을 엮어 경로 업데이트 / 쿼리를 진행하는 함수들이다. 전체적인 구조는 일반적인 HLD 구현과 같지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;yield를 통해서 영향을 받는 노드를 실시간으로 반환한다. (언어를 파이썬으로 선택한 이유이기도 하다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대략적인 흐름을 설명하자면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경로를 구하려는 두 노드가 속한 chain중 더 깊은 위치에 있는 chain 선택&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--&amp;gt; chain의 head(최상단 노드)까지 구간 쿼리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--&amp;gt; 해당 head의 부모가 속한 chain과, 아직 건드리지 않은 chain을 대상으로 앞의 절차 반복&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;------&amp;gt; 하나의 chain만 남게 된다면 마지막으로 구간 쿼리 후 종료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;pre id=&quot;code_1765013753242&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class ETT:
    # ETT용 클래스
    def __init__(self, n):
        self.n = n
        self.par = [0] * (n + 1)
        self.dep = [0] * (n + 1)
        self.in_time = [0] * (n + 1)
        self.out_time = [0] * (n + 1)
        self.g = [[] for _ in range(n + 1)]
        self.cnt = 0
        self.seg = SegTree(n)
        self.val = [0] * (n + 1)         # 노드 값
    
    def add_edge(self, u, v):
        self.g[u].append(v)
        self.g[v].append(u)
    
    def set_value(self, v, value):
        self.val[v] = value
    
    def dfs(self, v=1, p=0):
        # dfs ordering
        self.par[v] = p

        self.cnt += 1
        self.in_time[v] = self.cnt

        for child in self.g[v]:
            if child == p:
                continue

            self.dep[child] = self.dep[v] + 1
            self.dfs(child, v)

        self.out_time[v] = self.cnt
    
    def init(self):
        # ETT 초기화
        self.dep[1] = 0
        self.par[1] = 0
        self.dfs()
        
        for v in range(1, self.n + 1):
            self.seg.update(1,1,self.n, self.in_time[v],self.in_time[v],self.val[v])&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ETT 사전 설정 부분이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dfs 함수가 핵심으로, &lt;b&gt;2. iii&lt;/b&gt;에서 설명한것과 동일하게 in, out 배열을 채운다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;pre id=&quot;code_1765015803961&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    def query(self, v):
        affected_nodes = []
        
        # v를 루트로 하는 서브트리의 모든 노드 찾기
        def collect_subtree(node):
            affected_nodes.append(node)
            for child in self.g[node]:
                if child != self.par[node]:
                    collect_subtree(child)
        
        collect_subtree(v)
        result = self.seg.query(1, 1, self.n, self.in_time[v], self.out_time[v])
        yield (affected_nodes, 'QUERY', result)
    
    def update(self, v, val):
        affected_nodes = []
        
        # v를 루트로 하는 서브트리의 모든 노드 찾기
        def collect_subtree(node):
            affected_nodes.append(node)
            self.val[node] += val
            for child in self.g[node]:
                if child != self.par[node]:
                    collect_subtree(child)
        
        collect_subtree(v)
        self.seg.update(1, 1, self.n, self.in_time[v], self.out_time[v], val)
        yield (affected_nodes, 'UPDATE', val)
    
    def is_ancestor(self, u, v):
        #u가 v의 조상인지 확인
        return self.in_time[u] &amp;lt;= self.in_time[v] &amp;lt;= self.out_time[u]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브트리를 구간으로 업데이트 / 쿼리를 진행하는 함수이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상당히 간단한 구조로 동작하는데, 위에서 말했듯 ETT는 HLD와 다르게 서브트리를 바로 구간으로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표현할 수 있기 때문에, 쿼리를 한번만 날려도 되기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부함수 collect_subtree는 시각화를 위해 쿼리에 영향을 받는 노드를 찾는 용도이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;render.py&lt;/h4&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;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;pre id=&quot;code_1765017043642&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    def draw_edge(self, u, v, color, width, highlight=False):
        # 간선 그리기
        if u not in self.positions or v not in self.positions:
            return
        
        pos_u = self.positions[u]
        pos_v = self.positions[v]
        
        if highlight:
            # 하이라이트 효과
            highlight_color = HIGHLIGHT_COLOR_QUERY if self.highlight_type == 'QUERY' else HIGHLIGHT_COLOR_UPDATE
            pygame.draw.line(self.screen, highlight_color, pos_u, pos_v, width + 6)
        
        pygame.draw.line(self.screen, color, pos_u, pos_v, width)
    
    def draw_node(self, v, color, highlight=False):
        # 노드 그리기
        if v not in self.positions:
            return
        
        pos = self.positions[v]
        
        # 하이라이트 효과
        if highlight:
            highlight_color = HIGHLIGHT_COLOR_QUERY if self.highlight_type == 'QUERY' else HIGHLIGHT_COLOR_UPDATE
            pygame.draw.circle(self.screen, highlight_color, pos, NODE_RADIUS + 7)
        
        # 노드 외곽선
        pygame.draw.circle(self.screen, BLACK, pos, NODE_RADIUS + 2)
        # 노드 내부
        pygame.draw.circle(self.screen, color, pos, NODE_RADIUS)
        
        # 노드 값 표시
        value_text = self.value_font.render(str(self.tree.val[v]), True, WHITE)
        value_rect = value_text.get_rect(center=pos)
        self.screen.blit(value_text, value_rect)
        
        # 노드 번호는 작게 위에 표시
        label_text = self.font.render(f&quot;#{v}&quot;, True, BLACK)
        label_pos = (pos[0] - 15, pos[1] - NODE_RADIUS - 20)
        self.screen.blit(label_text, label_pos)
    
    def draw_tree(self):
        # 전체 트리 그리기

        # 간선 먼저 그리기
        for v in range(1, self.tree.n + 1):
            parent = self.tree.par[v]
            if parent != 0:
                edge_color = LIGHT_COLOR
                edge_width = EDGE_WIDTH_LIGHT
                
                # HLD의 경우 heavy edge 강조
                if self.is_hld and self.tree.is_heavy(parent, v):
                    chain_id = self.tree.id[v]
                    color_idx = (chain_id - 1) % len(CHAIN_COLORS)
                    edge_color = CHAIN_COLORS[color_idx]
                    edge_width = EDGE_WIDTH_HEAVY
                
                # 간선 하이라이트: 양쪽 노드가 모두 하이라이트되어 있을 때만
                highlight = v in self.highlight_nodes and parent in self.highlight_nodes
                self.draw_edge(parent, v, edge_color, edge_width, highlight)
        
        # 노드 그리기
        for v in range(1, self.tree.n + 1):
            node_color = BLUE
            
            # HLD의 경우 체인별 색상
            if self.is_hld:
                chain_id = self.tree.id[v]
                color_idx = (chain_id - 1) % len(CHAIN_COLORS)
                node_color = CHAIN_COLORS[color_idx]
            
            highlight = v in self.highlight_nodes
            self.draw_node(v, node_color, highlight)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간선, 노드, 트리를 그리는 함수이다. hld인 경우 노드색을 chainID별로 다르게 했고,&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;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;pre id=&quot;code_1765028408520&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    def run(self):
        running = True
        selected_node = 1
        clock = pygame.time.Clock()
        
        # 쿼리 모드 상태
        query_mode = None  # None, 'UPDATE', 'QUERY'
        query_state = 'IDLE'  # 'IDLE', 'SELECT_FIRST', 'SELECT_SECOND', 'INPUT_VALUE', 'EXECUTING'
        query_node1 = 0
        query_node2 = 0
        query_value = &quot;&quot;
        query_generator = None
        
        while running:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    running = False
                
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_ESCAPE:
                        if query_state != 'IDLE':
                            # 쿼리 모드 취소
                            query_mode = None
                            query_state = 'IDLE'
                            query_node1 = 0
                            query_node2 = 0
                            query_value = &quot;&quot;
                            self.set_highlight([])
                        else:
                            running = False
                    
                    # 쿼리 모드 시작
                    elif event.key == pygame.K_u and query_state == 'IDLE':
                        query_mode = 'UPDATE'
                        query_state = 'SELECT_FIRST'
                        query_value = &quot;&quot;
                    
                    elif event.key == pygame.K_q and query_state == 'IDLE':
                        query_mode = 'QUERY'
                        query_state = 'SELECT_FIRST'
                    
                    # 값 입력 모드
                    elif query_state == 'INPUT_VALUE':
                        if event.key == pygame.K_RETURN:
                            if query_value:
                                val = int(query_value)
                                if self.is_hld:
                                    if query_mode == 'UPDATE':
                                        query_generator = self.tree.update(query_node1, query_node2, val)
                                    else:
                                        query_generator = self.tree.query(query_node1, query_node2)
                                else:
                                    # ETT는 단일 노드만 사용
                                    if query_mode == 'UPDATE':
                                        query_generator = self.tree.update(query_node1, val)
                                    else:
                                        query_generator = self.tree.query(query_node1)
                                query_state = 'EXECUTING'
                                self.query_result = None
                        elif event.key == pygame.K_BACKSPACE:
                            query_value = query_value[:-1]
                        elif event.unicode.isdigit() or (event.unicode == '-' and not query_value):
                            query_value += event.unicode
                    # 실행중
                    elif query_state == 'EXECUTING' and event.key == pygame.K_RETURN:
                        if query_generator:
                            try:
                                nodes, op_type, result = next(query_generator)
                                self.set_highlight(nodes, op_type)
                                self.query_result = result
                            except StopIteration:
                                # 쿼리 완료
                                query_mode = None
                                query_state = 'IDLE'
                                query_generator = None
                                self.set_highlight([])
                                self.query_result = None
                
                # 노드 클릭
                if event.type == pygame.MOUSEBUTTONDOWN:
                    mouse_pos = pygame.mouse.get_pos()
                    clicked_node = self.find_clicked_node(mouse_pos)
                    
                    if clicked_node &amp;gt; 0:
                        if query_state == 'SELECT_FIRST':
                            query_node1 = clicked_node
                            # ETT는 단일 노드만 필요
                            if self.is_hld and (query_mode == 'QUERY' or query_mode == 'UPDATE'):
                                query_state = 'SELECT_SECOND'
                            else:
                                # ETT는 바로 값 입력 또는 쿼리 실행
                                if query_mode == 'UPDATE':
                                    query_state = 'INPUT_VALUE'
                                else:
                                    # 쿼리는 바로 실행
                                    query_generator = self.tree.query(query_node1)
                                    query_state = 'EXECUTING'
                            selected_node = clicked_node
                        elif query_state == 'SELECT_SECOND':
                            query_node2 = clicked_node
                            if query_mode == 'UPDATE':
                                query_state = 'INPUT_VALUE'
                            else:
                                # 쿼리는 바로 실행
                                if self.is_hld:
                                    query_generator = self.tree.query(query_node1, query_node2)
                                query_state = 'EXECUTING'
                            selected_node = clicked_node
                        else:
                            selected_node = clicked_node
            
            # 화면 그리기
            self.screen.fill(WHITE)
            self.draw_title()
            self.draw_tree()
            self.draw_info(selected_node)
            
            if query_state != 'IDLE':
                self.draw_query_panel(query_state, query_node1, query_node2, query_value)
            
            pygame.display.flip()
            clock.tick(60)
        
        pygame.quit()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체적인 구동을 담당하는 함수이다.. 사실 코드 길이에 비해 엄청난 핵심 코드는 아니기 때문에 가볍게 보고 넘어가자..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;main.py&lt;/h4&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;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;pre id=&quot;code_1765028590818&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from tree import HLD, ETT
from render import TreeRenderer

def create_tree():
    n = 13
    edges = [
        (1, 2),
        (1, 3),
        (1, 4),
        (2, 5),
        (2, 6),
        (3, 7),
        (3, 8),
        (4, 9),
        (6, 10),
        (6, 11),
        (8, 12),
        (9, 13)
    ]
    return n, edges

def build_hld_tree(n, edges):
    hld = HLD(n)
    
    for v in range(1, n + 1):
        hld.set_value(v, v)
    
    for u, v in edges:
        hld.add_edge(u, v)
    
    hld.init()
    
    return hld


def build_ett_tree(n, edges):
    ett = ETT(n)

    for v in range(1, n + 1):
        ett.set_value(v, v)
    
    for u, v in edges:
        ett.add_edge(u, v)
    
    ett.init()
    
    return ett&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&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;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;pre id=&quot;code_1765028668974&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def main():
    print(&quot;=&quot; * 50)
    print(&quot;HLD &amp;amp; ETT 시각화 프로그램&quot;)
    print(&quot;=&quot; * 50)
        
    n, edges = create_tree()

    # 시각화 모드 선택
    print(&quot;\n시각화 모드:&quot;)
    print(&quot;1. HLD (Heavy-Light Decomposition)&quot;)
    print(&quot;2. ETT (Euler Tour Tree)&quot;)
    
    mode = input(&quot;\n선택 (1-2, 기본값 1): &quot;).strip()
    
    if mode == '2':
        # ETT 모드
        ett = build_ett_tree(n, edges)
        
        print(&quot;\n&quot; + &quot;=&quot; * 50)
        print(&quot;ETT 시각화를 시작합니다...&quot;)
        print(&quot;=&quot; * 50)
        
        renderer = TreeRenderer(ett, is_hld=False)
        renderer.run()
    else:
        # HLD 모드
        hld = build_hld_tree(n, edges)
        
        print(&quot;\n&quot; + &quot;=&quot; * 50)
        print(&quot;HLD 시각화를 시작합니다...&quot;)
        print(&quot;=&quot; * 50)
        
        renderer = TreeRenderer(hld, is_hld=True)
        renderer.run()

if __name__ == &quot;__main__&quot;:
    main()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main함수이다. 어떤 모드를 사용할지 입력받고 모드에 따라 화면을 띄운다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;전체 코드&lt;/h4&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;pre id=&quot;code_1765091150056&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from tree import HLD, ETT
from render import TreeRenderer

def create_tree():
    n = 13
    edges = [
        (1, 2),
        (1, 3),
        (1, 4),
        (2, 5),
        (2, 6),
        (3, 7),
        (3, 8),
        (4, 9),
        (6, 10),
        (6, 11),
        (8, 12),
        (9, 13)
    ]
    return n, edges

def build_hld_tree(n, edges):
    hld = HLD(n)
    
    for v in range(1, n + 1):
        hld.set_value(v, v)
    
    for u, v in edges:
        hld.add_edge(u, v)
    
    hld.init()
    
    return hld


def build_ett_tree(n, edges):
    ett = ETT(n)

    for v in range(1, n + 1):
        ett.set_value(v, v)
    
    for u, v in edges:
        ett.add_edge(u, v)
    
    ett.init()
    
    return ett


def main():
    print(&quot;=&quot; * 50)
    print(&quot;HLD &amp;amp; ETT 시각화 프로그램&quot;)
    print(&quot;=&quot; * 50)
        
    n, edges = create_tree()

    # 시각화 모드 선택
    print(&quot;\n시각화 모드:&quot;)
    print(&quot;1. HLD (Heavy-Light Decomposition)&quot;)
    print(&quot;2. ETT (Euler Tour Tree)&quot;)
    
    mode = input(&quot;\n선택 (1-2, 기본값 1): &quot;).strip()
    
    if mode == '2':
        # ETT 모드
        ett = build_ett_tree(n, edges)
        
        print(&quot;\n&quot; + &quot;=&quot; * 50)
        print(&quot;ETT 시각화를 시작합니다...&quot;)
        print(&quot;=&quot; * 50)
        
        renderer = TreeRenderer(ett, is_hld=False)
        renderer.run()
    else:
        # HLD 모드
        hld = build_hld_tree(n, edges)
        
        print(&quot;\n&quot; + &quot;=&quot; * 50)
        print(&quot;HLD 시각화를 시작합니다...&quot;)
        print(&quot;=&quot; * 50)
        
        renderer = TreeRenderer(hld, is_hld=True)
        renderer.run()


if __name__ == &quot;__main__&quot;:
    main()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main.py&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;pre id=&quot;code_1765091197456&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class SegTree:
    # 구간업데이트 구간쿼리 합 세그먼트 트리
    def __init__(self, n):
        self.n = n
        self.tree = [0] * (4 * n + 16)
        self.lazy = [0] * (4 * n + 16)
    
    def push(self, idx, left, right):
        if self.lazy[idx] != 0:
            self.tree[idx] += self.lazy[idx] * (right - left + 1)
            if left != right:
                self.lazy[idx*2] += self.lazy[idx]
                self.lazy[idx*2 + 1] += self.lazy[idx]
            self.lazy[idx] = 0

    def update(self, idx, left, right, cleft, cright, cval):
        self.push(idx, left, right)
        if cright &amp;lt; left or right &amp;lt; cleft:
            return
        if cleft &amp;lt;= left and right &amp;lt;= cright:
            self.lazy[idx] += cval
            self.push(idx, left, right)
            return
        mid = (left + right) // 2
        self.update(idx*2, left, mid, cleft, cright, cval)
        self.update(idx*2+1, mid+1, right, cleft, cright, cval)
        self.push(idx*2, left, mid)
        self.push(idx*2+1, mid+1, right)
        self.tree[idx] = self.tree[idx*2] + self.tree[idx*2+1]
        
    def query(self, idx, left, right, qleft, qright):
        self.push(idx, left, right)
        if qright &amp;lt; left or right &amp;lt; qleft:
            return 0
        if qleft &amp;lt;= left and right &amp;lt;= qright:
            return self.tree[idx]
        mid = (left + right) // 2
        return self.query(idx*2, left, mid, qleft, qright) + self.query(idx*2+1, mid+1, right, qleft, qright)


class HLD:
    # HLD용 클래스
    def __init__(self, n):
        # HLD 구현용
        self.n = n
        self.size = [0] * (n + 1)        # 서브트리 크기
        self.dep = [0] * (n + 1)         # 깊이
        self.par = [0] * (n + 1)         # 부모 노드
        self.top = [0] * (n + 1)         # 체인의 최상단 정점
        self.in_time = [0] * (n + 1)     # ETT 시작 시간
        self.out_time = [0] * (n + 1)    # ETT 종료 시간
        self.g = [[] for _ in range(n + 1)]  # 그래프
        self.cnt = 0                     # ETT용
        self.seg = SegTree(n)
        
        # 시각화용
        self.id = [0] * (n + 1)          # 체인 ID
        self.chain_cnt = 0               # 체인 개수
        self.val = [0] * (n + 1)         # 노드 값
    
    def add_edge(self, u, v):
        # 간선 추가
        self.g[u].append(v)
        self.g[v].append(u)
    
    def set_value(self, v, value):
        # main에서 이 함수를 통해 값을 넘겨줄 것임
        self.val[v] = value
    
    def dfs1(self, v=1, p=0):
        # par, size, dep 저장, heavy 간선 앞으로 보내기
        self.par[v] = p
        self.size[v] = 1
        for i in range(len(self.g[v])):
            child = self.g[v][i]
            if child == p:
                continue
            self.dep[child] = self.dep[v] + 1
            self.par[child] = v
            self.dfs1(child, v)
            self.size[v] += self.size[child]
            if self.g[v][0] == p or self.size[child] &amp;gt; self.size[self.g[v][0]]:
                self.g[v][0], self.g[v][i] = self.g[v][i], self.g[v][0]
    
    def dfs2(self, v=1, p=0):
        # dfs ordering
        self.cnt += 1
        self.in_time[v] = self.cnt
        
        # 체인 ID 부여
        if v == self.top[v]:
            self.chain_cnt += 1
        self.id[v] = self.chain_cnt
        
        for child in self.g[v]:
            if child == p:
                continue
            # Heavy edge면 같은 체인, 아니면 새로운 체인 시작
            self.top[child] = self.top[v] if child == self.g[v][0] else child
            self.dfs2(child, v)
        self.out_time[v] = self.cnt
    
    def init(self):
        # HLD 초기화
        self.dep[1] = 0
        self.par[1] = 0
        self.top[1] = 1
        self.dfs1()
        self.dfs2()
        
        # 입력받은 값으로 세그트리 리프노드 채우기
        for v in range(1, self.n + 1):
            self.seg.update(1,1,self.n,self.in_time[v],self.in_time[v], self.val[v])
        


    def update(self, u, v, val):
        affected_nodes = []
        
        while self.top[u] != self.top[v]:
            if self.dep[self.top[u]] &amp;lt; self.dep[self.top[v]]:
                u, v = v, u
            head = self.top[u]
            
            for node in range(1,self.n+1):
                if self.in_time[head] &amp;lt;= self.in_time[node] &amp;lt;= self.in_time[u]:
                    affected_nodes += [node]
                    self.val[node] += val
                
            yield (affected_nodes.copy(), 'UPDATE', val)
            self.seg.update(1, 1, self.n, self.in_time[head], self.in_time[u], val)
            u = self.par[head]
        
        if self.dep[u] &amp;gt; self.dep[v]:
            u, v = v, u
        

        for node in range(1,self.n+1):
            if self.in_time[u] &amp;lt;= self.in_time[node] &amp;lt;= self.in_time[v]:
                affected_nodes += [node]
                self.val[node] += val

        yield (affected_nodes.copy(), 'UPDATE', val)
        self.seg.update(1, 1, self.n, self.in_time[u], self.in_time[v], val)
    
    def query(self, u, v):
        affected_nodes = []
        res = 0
        
        while self.top[u] != self.top[v]:
            if self.dep[self.top[u]] &amp;lt; self.dep[self.top[v]]:
                u, v = v, u
            head = self.top[u]
            
            for node in range(1,self.n+1):
                if self.in_time[head] &amp;lt;= self.in_time[node] &amp;lt;= self.in_time[u]:
                    affected_nodes += [node]
            
            res += self.seg.query(1, 1, self.n, self.in_time[head], self.in_time[u])
            yield (affected_nodes.copy(), 'QUERY', res)
            u = self.par[head]
        
        if self.dep[u] &amp;gt; self.dep[v]:
            u, v = v, u
        
        for node in range(1,self.n+1):
            if self.in_time[u] &amp;lt;= self.in_time[node] &amp;lt;= self.in_time[v]:
                affected_nodes += [node]

        res += self.seg.query(1, 1, self.n, self.in_time[u], self.in_time[v])
        yield (affected_nodes.copy(), 'QUERY', res)
    

    def is_heavy(self, u, v):
        # heavy child인지 검사
        if self.dep[u] &amp;gt; self.dep[v]:
            u, v = v, u
        return self.par[v] == u and len(self.g[u]) &amp;gt; 0 and self.g[u][0] == v
    

class ETT:
    # ETT용 클래스
    def __init__(self, n):
        self.n = n
        self.par = [0] * (n + 1)
        self.dep = [0] * (n + 1)
        self.in_time = [0] * (n + 1)
        self.out_time = [0] * (n + 1)
        self.g = [[] for _ in range(n + 1)]
        self.cnt = 0
        self.seg = SegTree(n)
        self.val = [0] * (n + 1)         # 노드 값
    
    def add_edge(self, u, v):
        self.g[u].append(v)
        self.g[v].append(u)
    
    def set_value(self, v, value):
        # main에서 이 함수를 통해 값을 넘겨줌
        self.val[v] = value
    
    def dfs(self, v=1, p=0):
        # dfs ordering
        self.par[v] = p

        self.cnt += 1
        self.in_time[v] = self.cnt

        for child in self.g[v]:
            if child == p:
                continue

            self.dep[child] = self.dep[v] + 1
            self.dfs(child, v)

        self.out_time[v] = self.cnt
    
    def init(self):
        # ETT 초기화
        self.dep[1] = 0
        self.par[1] = 0
        self.dfs()
        
        for v in range(1, self.n + 1):
            self.seg.update(1,1,self.n, self.in_time[v],self.in_time[v],self.val[v])
    
    def query(self, v):
        affected_nodes = []
        
        # v를 루트로 하는 서브트리의 모든 노드 찾기
        def collect_subtree(node):
            affected_nodes.append(node)
            for child in self.g[node]:
                if child != self.par[node]:
                    collect_subtree(child)
        
        collect_subtree(v)
        result = self.seg.query(1, 1, self.n, self.in_time[v], self.out_time[v])
        yield (affected_nodes, 'QUERY', result)
    
    def update(self, v, val):
        affected_nodes = []
        
        # v를 루트로 하는 서브트리의 모든 노드 찾기
        def collect_subtree(node):
            affected_nodes.append(node)
            self.val[node] += val
            for child in self.g[node]:
                if child != self.par[node]:
                    collect_subtree(child)
        
        collect_subtree(v)
        self.seg.update(1, 1, self.n, self.in_time[v], self.out_time[v], val)
        yield (affected_nodes, 'UPDATE', val)
    
    def is_ancestor(self, u, v):
        #u가 v의 조상인지 확인
        return self.in_time[u] &amp;lt;= self.in_time[v] &amp;lt;= self.out_time[u]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tree.py&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;pre id=&quot;code_1765091298205&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import pygame
from tree import HLD, ETT

# 색상 정의
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
BLUE = (0, 0, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
YELLOW = (255, 255, 0)
ORANGE = (255, 165, 0)

# 체인별 색상
CHAIN_COLORS = [
    (255, 99, 71),    # Tomato
    (60, 179, 113),   # MediumSeaGreen
    (65, 105, 225),   # RoyalBlue
    (255, 165, 0),    # Orange
    (147, 112, 219),  # MediumPurple
    (0, 191, 255),    # DeepSkyBlue
    (255, 0, 255),    # Magenta
    (128, 0, 0),      # Maroon
    (255, 215, 0),    # Gold
    (46, 139, 87)     # SeaGreen
]

LIGHT_COLOR = (128, 128, 128)
HIGHLIGHT_COLOR_QUERY = (50, 205, 50)  # 파란색 - 쿼리
HIGHLIGHT_COLOR_UPDATE = (255, 140, 0)  # 주황색 - 업데이트

# 렌더링 설정
NODE_RADIUS = 25
EDGE_WIDTH_HEAVY = 4
EDGE_WIDTH_LIGHT = 2

SCREEN_WIDTH = 1920
SCREEN_HEIGHT = 1080


def init_pygame():
    #pygame 초기화
    pygame.init()
    # screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
    screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.SCALED | pygame.FULLSCREEN)
    pygame.display.set_caption(&quot;HLD &amp;amp; ETT Visualization&quot;)
    return screen


class TreeRenderer:
    # 트리 렌더링해주는 클래스
    
    def __init__(self, tree_data, is_hld=True):
        self.tree = tree_data
        self.is_hld = is_hld
        self.screen = init_pygame()
        self.font = pygame.font.SysFont('malgungothic', 18)
        self.title_font = pygame.font.SysFont('malgungothic', 28, bold=True)
        self.value_font = pygame.font.SysFont('arial', 16, bold=True)
        
        self.positions = {}
        self.highlight_nodes = set()  # 하이라이트할 노드들
        self.highlight_type = 'QUERY'  # 'QUERY' or 'UPDATE'
        self.query_result = None  # 쿼리 결과
        
        self._calc_positions()
    
    def _calc_positions(self):
        if self.tree.n == 0:
            return
        
        # 각 깊이별 노드 개수 세기
        depth_nodes = {}
        for v in range(1, self.tree.n + 1):
            depth = self.tree.dep[v]
            if depth not in depth_nodes:
                depth_nodes[depth] = []
            depth_nodes[depth].append(v)
        
        max_depth = max(depth_nodes.keys()) if depth_nodes else 0
        
        # y 좌표 계산
        y_gap = (SCREEN_HEIGHT - 300) / (max_depth + 1) if max_depth &amp;gt; 0 else SCREEN_HEIGHT - 300
        
        # 각 깊이별로 노드 배치
        for depth, nodes in depth_nodes.items():
            num_nodes = len(nodes)
            x_gap = (SCREEN_WIDTH - 200) / (num_nodes + 1)
            
            for idx, node in enumerate(nodes):
                x = 100 + x_gap * (idx + 1)
                y = 100 + y_gap * depth
                self.positions[node] = (int(x), int(y))
    
    def draw_edge(self, u, v, color, width, highlight=False):
        # 간선 그리기
        if u not in self.positions or v not in self.positions:
            return
        
        pos_u = self.positions[u]
        pos_v = self.positions[v]
        
        if highlight:
            # 하이라이트 효과
            highlight_color = HIGHLIGHT_COLOR_QUERY if self.highlight_type == 'QUERY' else HIGHLIGHT_COLOR_UPDATE
            pygame.draw.line(self.screen, highlight_color, pos_u, pos_v, width + 6)
        
        pygame.draw.line(self.screen, color, pos_u, pos_v, width)
    
    def draw_node(self, v, color, highlight=False):
        # 노드 그리기
        if v not in self.positions:
            return
        
        pos = self.positions[v]
        
        # 하이라이트 효과
        if highlight:
            highlight_color = HIGHLIGHT_COLOR_QUERY if self.highlight_type == 'QUERY' else HIGHLIGHT_COLOR_UPDATE
            pygame.draw.circle(self.screen, highlight_color, pos, NODE_RADIUS + 7)
        
        # 노드 외곽선
        pygame.draw.circle(self.screen, BLACK, pos, NODE_RADIUS + 2)
        # 노드 내부
        pygame.draw.circle(self.screen, color, pos, NODE_RADIUS)
        
        # 노드 값 표시
        value_text = self.value_font.render(str(self.tree.val[v]), True, WHITE)
        value_rect = value_text.get_rect(center=pos)
        self.screen.blit(value_text, value_rect)
        
        # 노드 번호는 작게 위에 표시
        label_text = self.font.render(f&quot;#{v}&quot;, True, BLACK)
        label_pos = (pos[0] - 15, pos[1] - NODE_RADIUS - 20)
        self.screen.blit(label_text, label_pos)
    
    def draw_tree(self):
        # 전체 트리 그리기

        # 간선 먼저 그리기
        for v in range(1, self.tree.n + 1):
            parent = self.tree.par[v]
            if parent != 0:
                edge_color = LIGHT_COLOR
                edge_width = EDGE_WIDTH_LIGHT
                
                # HLD의 경우 Heavy edge 강조
                if self.is_hld and self.tree.is_heavy(parent, v):
                    chain_id = self.tree.id[v]
                    color_idx = (chain_id - 1) % len(CHAIN_COLORS)
                    edge_color = CHAIN_COLORS[color_idx]
                    edge_width = EDGE_WIDTH_HEAVY
                
                # 간선 하이라이트: 양쪽 노드가 모두 하이라이트되어 있을 때만
                highlight = v in self.highlight_nodes and parent in self.highlight_nodes
                self.draw_edge(parent, v, edge_color, edge_width, highlight)
        
        # 노드 그리기
        for v in range(1, self.tree.n + 1):
            node_color = BLUE
            
            # HLD의 경우 체인별 색상
            if self.is_hld:
                chain_id = self.tree.id[v]
                color_idx = (chain_id - 1) % len(CHAIN_COLORS)
                node_color = CHAIN_COLORS[color_idx]
            
            highlight = v in self.highlight_nodes
            self.draw_node(v, node_color, highlight)
    
    def draw_info(self, selected_node):
        # 노드 정보 표시
        if selected_node == 0 or selected_node not in self.positions:
            return
        
        # 정보 패널 배경
        info_panel = pygame.Rect(10, SCREEN_HEIGHT - 250, 400, 240)
        pygame.draw.rect(self.screen, (240, 240, 240), info_panel)
        pygame.draw.rect(self.screen, BLACK, info_panel, 2)
        
        # 기본 정보
        info = [
            f&quot;선택된 노드: #{selected_node}&quot;,
            f&quot;노드 값: {self.tree.val[selected_node]}&quot;,
            f&quot;깊이 (depth): {self.tree.dep[selected_node]}&quot;,
            f&quot;부모 (parent): #{self.tree.par[selected_node]}&quot;,
            f&quot;ETT in: {self.tree.in_time[selected_node]}&quot;,
            f&quot;ETT out: {self.tree.out_time[selected_node]}&quot;
        ]
        
        # HLD 추가 정보
        if self.is_hld:
            info.append(f&quot;체인 ID: {self.tree.id[selected_node]}&quot;)
            info.append(f&quot;체인 Head: #{self.tree.top[selected_node]}&quot;)
            info.append(f&quot;서브트리 크기: {self.tree.size[selected_node]}&quot;)
        
        y_offset = SCREEN_HEIGHT - 240
        for i, text_str in enumerate(info):
            text = self.font.render(text_str, True, BLACK)
            self.screen.blit(text, (20, y_offset + i * 25))
    
    def draw_query_panel(self, query_mode, node1, node2, query_val):
        # 쿼리 패넗
        panel = pygame.Rect(SCREEN_WIDTH - 510, SCREEN_HEIGHT - 250, 500, 240)
        pygame.draw.rect(self.screen, (240, 240, 240), panel)
        pygame.draw.rect(self.screen, BLACK, panel, 2)
        
        y_offset = SCREEN_HEIGHT - 240
        
        # 제목
        title_text = &quot;======= 경로 쿼리 =======&quot; if self.is_hld else &quot;====== 서브트리 쿼리 ======&quot;
        title = self.font.render(title_text, True, BLACK)
        self.screen.blit(title, (SCREEN_WIDTH - 500, y_offset))
        y_offset += 30
        

        if query_mode == 'SELECT_FIRST':
            if self.is_hld:
                text = self.font.render(&quot;첫 번째 노드를 선택하세요&quot;, True, RED)
            else:
                text = self.font.render(&quot;서브트리의 루트 노드를 선택하세요&quot;, True, RED)
            self.screen.blit(text, (SCREEN_WIDTH - 500, y_offset))
        
        elif query_mode == 'SELECT_SECOND':
            text1 = self.font.render(f&quot;첫 번째 노드: #{node1}&quot;, True, BLACK)
            self.screen.blit(text1, (SCREEN_WIDTH - 500, y_offset))
            y_offset += 25
            text2 = self.font.render(&quot;두 번째 노드를 선택하세요&quot;, True, RED)
            self.screen.blit(text2, (SCREEN_WIDTH - 500, y_offset))
        
        elif query_mode == 'INPUT_VALUE':
            if self.is_hld:
                text1 = self.font.render(f&quot;노드: #{node1} -&amp;gt; #{node2}&quot;, True, BLACK)
            else:
                text1 = self.font.render(f&quot;서브트리 루트: #{node1}&quot;, True, BLACK)
            self.screen.blit(text1, (SCREEN_WIDTH - 500, y_offset))
            y_offset += 25
            text2 = self.font.render(f&quot;입력값: {query_val if query_val else ''}&quot;, True, BLUE)
            self.screen.blit(text2, (SCREEN_WIDTH - 500, y_offset))
            y_offset += 25
            text3 = self.font.render(&quot;값을 입력하고 Enter를 누르세요&quot;, True, RED)
            self.screen.blit(text3, (SCREEN_WIDTH - 500, y_offset))
        
        elif query_mode == 'EXECUTING':
            text1 = self.font.render(f&quot;쿼리 실행 중...&quot;, True, GREEN)
            self.screen.blit(text1, (SCREEN_WIDTH - 500, y_offset))
            y_offset += 25
            if self.query_result is not None:
                text2 = self.font.render(f&quot;결과: {self.query_result}&quot;, True, BLUE)
                self.screen.blit(text2, (SCREEN_WIDTH - 500, y_offset))
                y_offset += 25
            text3 = self.font.render(&quot;Enter: 완료&quot;, True, BLACK)
            self.screen.blit(text3, (SCREEN_WIDTH - 500, y_offset))
        
        # 단축키 안내
        y_offset = SCREEN_HEIGHT - 80
        text = self.font.render(&quot;ESC: 모드 취소&quot;, True, (100, 100, 100))
        self.screen.blit(text, (SCREEN_WIDTH - 500, y_offset))
    

    def draw_title(self):
        #제목
        title = &quot;Heavy-Light Decomposition&quot; if self.is_hld else &quot;Euler Tour Technique&quot;
        text = self.title_font.render(title, True, BLACK)
        text_rect = text.get_rect(center=(SCREEN_WIDTH // 2, 30))
        self.screen.blit(text, text_rect)
        
        # 조작 안내
        guide = &quot;클릭: 노드 선택 | U: 업데이트 | Q: 쿼리 | ESC: 종료&quot;
        guide_text = self.font.render(guide, True, (100, 100, 100))
        guide_rect = guide_text.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT - 20))
        self.screen.blit(guide_text, guide_rect)
    
    def find_clicked_node(self, mouse_pos):
        # 클린 노드 찾기
        min_dist = float('inf')
        closest_node = 0
        
        for node, pos in self.positions.items():
            dist_sq = (mouse_pos[0] - pos[0])**2 + (mouse_pos[1] - pos[1])**2
            if dist_sq &amp;lt; min_dist:
                min_dist = dist_sq
                closest_node = node
        
        # 노드 반경 내에 클릭했는지 확인
        if min_dist &amp;lt; (NODE_RADIUS * 2)**2:
            return closest_node
        return 0
    
    def set_highlight(self, nodes, op_type='QUERY'):
        # 하이라이트
        self.highlight_nodes = set(nodes) if nodes else set()
        self.highlight_type = op_type
    
    def run(self):
        running = True
        selected_node = 1
        clock = pygame.time.Clock()
        
        # 쿼리 모드 상태
        query_mode = None  # None, 'UPDATE', 'QUERY'
        query_state = 'IDLE'  # 'IDLE', 'SELECT_FIRST', 'SELECT_SECOND', 'INPUT_VALUE', 'EXECUTING'
        query_node1 = 0
        query_node2 = 0
        query_value = &quot;&quot;
        query_generator = None
        
        while running:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    running = False
                
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_ESCAPE:
                        if query_state != 'IDLE':
                            # 쿼리 모드 취소
                            query_mode = None
                            query_state = 'IDLE'
                            query_node1 = 0
                            query_node2 = 0
                            query_value = &quot;&quot;
                            self.set_highlight([])
                        else:
                            running = False
                    
                    # 쿼리 모드 시작
                    elif event.key == pygame.K_u and query_state == 'IDLE':
                        query_mode = 'UPDATE'
                        query_state = 'SELECT_FIRST'
                        query_value = &quot;&quot;
                    
                    elif event.key == pygame.K_q and query_state == 'IDLE':
                        query_mode = 'QUERY'
                        query_state = 'SELECT_FIRST'
                    
                    # 값 입력 모드
                    elif query_state == 'INPUT_VALUE':
                        if event.key == pygame.K_RETURN:
                            if query_value:
                                val = int(query_value)
                                if self.is_hld:
                                    if query_mode == 'UPDATE':
                                        query_generator = self.tree.update(query_node1, query_node2, val)
                                    else:
                                        query_generator = self.tree.query(query_node1, query_node2)
                                else:
                                    # ETT는 단일 노드만 사용
                                    if query_mode == 'UPDATE':
                                        query_generator = self.tree.update(query_node1, val)
                                    else:
                                        query_generator = self.tree.query(query_node1)
                                query_state = 'EXECUTING'
                                self.query_result = None
                        elif event.key == pygame.K_BACKSPACE:
                            query_value = query_value[:-1]
                        elif event.unicode.isdigit() or (event.unicode == '-' and not query_value):
                            query_value += event.unicode
                    # 실행중
                    elif query_state == 'EXECUTING' and event.key == pygame.K_RETURN:
                        if query_generator:
                            try:
                                nodes, op_type, result = next(query_generator)
                                self.set_highlight(nodes, op_type)
                                self.query_result = result
                            except StopIteration:
                                # 쿼리 완료
                                query_mode = None
                                query_state = 'IDLE'
                                query_generator = None
                                self.set_highlight([])
                                self.query_result = None
                
                # 노드 클릭
                if event.type == pygame.MOUSEBUTTONDOWN:
                    mouse_pos = pygame.mouse.get_pos()
                    clicked_node = self.find_clicked_node(mouse_pos)
                    
                    if clicked_node &amp;gt; 0:
                        if query_state == 'SELECT_FIRST':
                            query_node1 = clicked_node
                            # ETT는 단일 노드만 필요
                            if self.is_hld and (query_mode == 'QUERY' or query_mode == 'UPDATE'):
                                query_state = 'SELECT_SECOND'
                            else:
                                # ETT는 바로 값 입력 또는 쿼리 실행
                                if query_mode == 'UPDATE':
                                    query_state = 'INPUT_VALUE'
                                else:
                                    # 쿼리는 바로 실행
                                    query_generator = self.tree.query(query_node1)
                                    query_state = 'EXECUTING'
                            selected_node = clicked_node
                        elif query_state == 'SELECT_SECOND':
                            query_node2 = clicked_node
                            if query_mode == 'UPDATE':
                                query_state = 'INPUT_VALUE'
                            else:
                                # 쿼리는 바로 실행
                                if self.is_hld:
                                    query_generator = self.tree.query(query_node1, query_node2)
                                query_state = 'EXECUTING'
                            selected_node = clicked_node
                        else:
                            selected_node = clicked_node
            
            # 화면 그리기
            self.screen.fill(WHITE)
            self.draw_title()
            self.draw_tree()
            self.draw_info(selected_node)
            
            if query_state != 'IDLE':
                self.draw_query_panel(query_state, query_node1, query_node2, query_value)
            
            pygame.display.flip()
            clock.tick(60)
        
        pygame.quit()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;render.py&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 배운점, 느낀점 및 개선점&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HLD와 ETT를 처음으로 배울때도 너무 재밌는 아이디어라 생각했는데,&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;DFS ordering이나 ETT의 과정도 더 구체적인 단계로 보여주면 좋을 것 같다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HLD와 ETT 구현에 대해 설명이 더 필요하다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://mollusca0907.tistory.com/57&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2025.10.24 - [PS] - [백준] 13510 트리와 쿼리 1 - C++&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;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;</description>
      <category>Unifox</category>
      <category>unifox</category>
      <author>mo1lusca</author>
      <guid isPermaLink="true">https://mollusca0907.tistory.com/73</guid>
      <comments>https://mollusca0907.tistory.com/73#entry73comment</comments>
      <pubDate>Sun, 7 Dec 2025 16:12:10 +0900</pubDate>
    </item>
    <item>
      <title>[Codeforces] Round 1065 (Div. 3) 업솔빙</title>
      <link>https://mollusca0907.tistory.com/72</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://codeforces.com/contest/2171&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://codeforces.com/contest/2171&lt;/a&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;코포를 두세번정도 쳐봤었지만..&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; 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;style4&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222; text-align: center;&quot;&gt;A. Shizuku Hoshikawa and Farm Legs&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222; text-align: center;&quot;&gt;브론즈쯤 되는듯한 간단한 사칙연산 문제였다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;n이 홀수인 경우는 불가능하니 0이다. n이 짝수인 경우만 고려하자.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;소의 마릿수를 정하면 닭의 마릿수는 자동으로 정해진다. (소 1마리 == 닭 2마리)&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;n을 4로 나눈 값 + 1(소를 선택하지 않는 경우)가 정답이 되겠다.&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;pre id=&quot;code_1763901185666&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;include &amp;lt;bits/stdc++.h&amp;gt;
using namespace std;
 
int main() {
	int t;
	cin &amp;gt;&amp;gt; t;
	while (t--) {
		int n;
		cin &amp;gt;&amp;gt; n;
		if (n % 2) {
			cout &amp;lt;&amp;lt; &quot;0\n&quot;;
			continue;
		}
		cout &amp;lt;&amp;lt; n / 4 + 1 &amp;lt;&amp;lt; &quot;\n&quot;;
	}
	return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222; text-align: center;&quot;&gt;B. Yuu Koito and Minimum Absolute Sum&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222; text-align: center;&quot;&gt;빈칸을 임의의 값으로 채워주면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-mathml=&quot;&amp;lt;math xmlns=&amp;quot;http://www.w3.org/1998/Math/MathML&amp;quot;&amp;gt;&amp;lt;msub&amp;gt;&amp;lt;mi&amp;gt;b&amp;lt;/mi&amp;gt;&amp;lt;mi&amp;gt;i&amp;lt;/mi&amp;gt;&amp;lt;/msub&amp;gt;&amp;lt;mo&amp;gt;=&amp;lt;/mo&amp;gt;&amp;lt;msub&amp;gt;&amp;lt;mi&amp;gt;a&amp;lt;/mi&amp;gt;&amp;lt;mrow class=&amp;quot;MJX-TeXAtom-ORD&amp;quot;&amp;gt;&amp;lt;mi&amp;gt;i&amp;lt;/mi&amp;gt;&amp;lt;mo&amp;gt;+&amp;lt;/mo&amp;gt;&amp;lt;mn&amp;gt;1&amp;lt;/mn&amp;gt;&amp;lt;/mrow&amp;gt;&amp;lt;/msub&amp;gt;&amp;lt;mo&amp;gt;&amp;amp;#x2212;&amp;lt;/mo&amp;gt;&amp;lt;msub&amp;gt;&amp;lt;mi&amp;gt;a&amp;lt;/mi&amp;gt;&amp;lt;mi&amp;gt;i&amp;lt;/mi&amp;gt;&amp;lt;/msub&amp;gt;&amp;lt;/math&amp;gt;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;bi의 정의를 보고, |b1+b2+...+bn| 라는 식을 정리하면 |an - a1| 가 된다. 이 식을 최소화시키면 된다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;s&gt;&lt;span style=&quot;background-color: #ffffff; text-align: left;&quot; data-mathml=&quot;&amp;lt;math xmlns=&amp;quot;http://www.w3.org/1998/Math/MathML&amp;quot;&amp;gt;&amp;lt;msub&amp;gt;&amp;lt;mi&amp;gt;b&amp;lt;/mi&amp;gt;&amp;lt;mi&amp;gt;i&amp;lt;/mi&amp;gt;&amp;lt;/msub&amp;gt;&amp;lt;mo&amp;gt;=&amp;lt;/mo&amp;gt;&amp;lt;msub&amp;gt;&amp;lt;mi&amp;gt;a&amp;lt;/mi&amp;gt;&amp;lt;mrow class=&amp;quot;MJX-TeXAtom-ORD&amp;quot;&amp;gt;&amp;lt;mi&amp;gt;i&amp;lt;/mi&amp;gt;&amp;lt;mo&amp;gt;+&amp;lt;/mo&amp;gt;&amp;lt;mn&amp;gt;1&amp;lt;/mn&amp;gt;&amp;lt;/mrow&amp;gt;&amp;lt;/msub&amp;gt;&amp;lt;mo&amp;gt;&amp;amp;#x2212;&amp;lt;/mo&amp;gt;&amp;lt;msub&amp;gt;&amp;lt;mi&amp;gt;a&amp;lt;/mi&amp;gt;&amp;lt;mi&amp;gt;i&amp;lt;/mi&amp;gt;&amp;lt;/msub&amp;gt;&amp;lt;/math&amp;gt;&quot;&gt;만조분의 맛..&lt;/span&gt;&lt;/s&gt;&lt;/span&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;pre id=&quot;code_1763901528826&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;bits/stdc++.h&amp;gt;
using namespace std;
 
int main() {
	int t;
	cin &amp;gt;&amp;gt; t;
	while (t--) {
		int n;
		cin &amp;gt;&amp;gt; n;
		vector&amp;lt;int&amp;gt;v(n);
		for (int i = 0; i &amp;lt; n; i++) {
			cin &amp;gt;&amp;gt; v[i];
			if (i != 0 &amp;amp;&amp;amp; i != n - 1) {
				if (v[i] == -1) {
					v[i] = 0;
				}
			}
		}
		if (v[0] == -1 &amp;amp;&amp;amp; v[n - 1] == -1) {
			v[0] = v[n - 1] = 0;
		}
		else if (v[0] == -1 &amp;amp;&amp;amp; v[n - 1] != -1) {
			v[0] = v[n - 1];
		}
		else if (v[0] != -1 &amp;amp;&amp;amp; v[n - 1] == -1) {
			v[n - 1] = v[0];
		}
		cout &amp;lt;&amp;lt; abs(v[n - 1] - v[0]) &amp;lt;&amp;lt; &quot;\n&quot;;
		for (int i = 0; i &amp;lt; n; i++) {
			cout &amp;lt;&amp;lt; v[i] &amp;lt;&amp;lt; &quot; &quot;;
		}
		cout &amp;lt;&amp;lt; &quot;\n&quot;;
	}
	return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222; text-align: center;&quot;&gt;C1. Renako Amaori and XOR Game (easy version)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222; text-align: center;&quot;&gt;모든 2n개의 원소의 XOR 값은 게임을 아무리 진행해도 변하지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222; text-align: center;&quot;&gt;또한 이 값은 Ajisai와 Mai의 점수의 XOR과 같고, 이게 0이라면 Tie라는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222; text-align: center;&quot;&gt;0이 아니라고 해보자. 게임의 승패를 정하는 턴은 가장 마지막으로 a[i] != b[i]인 턴일것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222; text-align: center;&quot;&gt;이때의 i가 홀수면 Ajisai, 짝수면 Mai가 이긴다.&lt;/span&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;pre id=&quot;code_1764206933450&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;bits/stdc++.h&amp;gt;
using namespace std;

int main() {
	int t;
	cin &amp;gt;&amp;gt; t;
	while (t--) {
		int n;
		cin &amp;gt;&amp;gt; n;
		vector&amp;lt;int&amp;gt;a(n);
		vector&amp;lt;int&amp;gt;b(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++) {
			cin &amp;gt;&amp;gt; b[i];
		}
		int total = 0;
		int idx = 0;
		for (int i = 1; i &amp;lt;= n; i++) {
			total ^= (a[i - 1] ^ b[i - 1]);
			if (a[i - 1] != b[i - 1]) {
				idx = i;
			}
		}
		if (total==0) {
			cout &amp;lt;&amp;lt; &quot;tie\n&quot;;
		}
		else {
			if (idx % 2 == 1) {
				cout &amp;lt;&amp;lt; &quot;ajisai\n&quot;;
			}
			else {
				cout &amp;lt;&amp;lt; &quot;mai\n&quot;;
			}
		}
	}
	return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;풀고 나니까 쉬운 문제인데 막상 할때는 너무 어려웠다..&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; 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;</description>
      <category>PS</category>
      <category>C++</category>
      <category>Codeforces</category>
      <category>PS</category>
      <author>mo1lusca</author>
      <guid isPermaLink="true">https://mollusca0907.tistory.com/72</guid>
      <comments>https://mollusca0907.tistory.com/72#entry72comment</comments>
      <pubDate>Thu, 27 Nov 2025 10:31:35 +0900</pubDate>
    </item>
    <item>
      <title>[Unifox] - 웹해킹 2차시</title>
      <link>https://mollusca0907.tistory.com/67</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요 오늘은 Blind SQL Injection에 대해 알아볼게요.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;text-align: center;&quot; data-ke-size=&quot;size23&quot;&gt;Blind SQL injection이란??&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;주입한 SQL 쿼리의 결과가 보이지 않을 때 사용하는 방법이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;쿼리의 결과를 볼 수 없기 때문에, True/False 값을 가지는 쿼리를 날려 페이지의 변화를 보거나(Boolean-Based),&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;딜레이를 거는 쿼리 등을 날려서(Time-Based) 쿼리의 동작 여부같은걸 확인한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;h3 style=&quot;text-align: center;&quot; data-ke-size=&quot;size23&quot;&gt;Boolean-Based SQL injection이란??????&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Blind SQL injection의 한 종류로, &lt;b&gt;쿼리에 대한 True/False 결과&lt;/b&gt;로 인한 서버나 사이트 변화를 통해서 &lt;b&gt;데이터를 유추&lt;/b&gt;한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;쉽게 예를 들자면, admin의 비밀번호를 브루트포스를 통해 뚫는다고 할 때, 비밀번호의 자릿수를 알아내는데에 사용 할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;업-다운 게임이나 이분탐색 처럼 기준값과 비교 한 True/False의 결과를 통해 비밀번호 자릿수의 범위를 좁힌다..&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;정보 획득을 위해 많은 쿼리를 전송해야 하고, 자동화를 위해 스크립트를 작성하기도 한다..&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: center;&quot; data-ke-size=&quot;size23&quot;&gt;Time-Based SQL injection이란????&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;사이트의 변화를 통해 &lt;b&gt;쿼리 결과의 True/False를 구분할 수 없을 때&lt;/b&gt; 사용된다..&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Sleep 등의 함수를 사용해 &lt;b&gt;응답 별 딜레이를 다르게&lt;/b&gt; 걸어서 결과를 구분한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;and 연산자&lt;/b&gt;와 함께 사용해서 Sleep을 걸 조건을 정할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1762445025000&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;admin' and sleep(10) -- -&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;대충 where userid = '{input}' 라고 받는 상황이라고 생각하자.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;admin이라는 userid가 있다면, 앞의 조건에서는 True가 뜨고, Sleep 함수를 실행하게 되어&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;10초동안 딜레이가 걸리는 걸 볼 수 있을 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(만약 거짓이라면, and 연산자는 일반적으로 앞의 조건이 거짓일 때&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;뒤의 조건을 검사하지 않기 때문에 Sleep 함수가 실행되지 않을 것이다.)&lt;/span&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;문제를 풀어봐야 이해가 더 잘 될 것 같다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;SQL injection의 고수가 되자..&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;</description>
      <category>Unifox</category>
      <category>unifox</category>
      <author>mo1lusca</author>
      <guid isPermaLink="true">https://mollusca0907.tistory.com/67</guid>
      <comments>https://mollusca0907.tistory.com/67#entry67comment</comments>
      <pubDate>Fri, 7 Nov 2025 01:13:14 +0900</pubDate>
    </item>
    <item>
      <title>[Unifox] - 웹해킹 1차시</title>
      <link>https://mollusca0907.tistory.com/60</link>
      <description>&lt;p style=&quot;text-align: center;&quot; 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;h3 style=&quot;text-align: center;&quot; data-ke-size=&quot;size23&quot;&gt;SOP (Same-Origin Policy) 란???&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;웹상에서 다른 출처로의 리소스 요청을 제한하는 정책이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 출처(Origin)이란, 프로토콜 + 도메인 + 포트를 말한다..&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 이 세 요소 중 하나라도 다르면 다른 Origin으로 간주한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;만약 SOP가 없다면??? 어떤 일이 일어날까?!??!??!!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;극단적인 예시를 들어보자. 온라인뱅킹 사이트의 세션 쿠키가 남아있는 상태에서&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;임의의 악성 사이트에 접속하게 되면, 그 사이트가 쿠키를 멋대로 써서&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;내 계좌에서 돈을 마구 뽑아버릴수도 있다!!!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이런 불의의 사고를 막기 위해.. SOP가 만들어진것이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;h3 style=&quot;text-align: center;&quot; data-ke-size=&quot;size23&quot;&gt;XSS (Cross-Site Scripting) 란???&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;웹사이트에 악성 스크립트를 넣어서 실행시키거나 세션을 탈취하는 공격이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;주로 JavaScript를 이용해 이루어지며, SQL Injection과 함께 가장 기초적인 취약점으로 알려져 있다..&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;공격 종류는 크게 세가지이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; Reflected XSS&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;공격자가 악성 스크립트를 직접 클라이언트에게 전달하는 방식이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;주로 악성 스크립트를 URL에 포함시켜 공격한다..&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Stored XSS&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;공격자가 악성 스크립트를 서버에 저장시키고, 클라이언트와의 요청&amp;amp;응답을 통해 공격한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;게시글 작성 등을 통해 서버에 악성 스크립트를 저장 시킬 수 있다..&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;서버의 필터링으로 인해 우회하기 어렵지만 한번 성공하면 광범위한 피해를 준다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;DOM Based XSS&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;공격자가 클라이언트의 DOM을 조작하는 공격이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; 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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;기다려라 XSS 내가 간다&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;</description>
      <category>Unifox</category>
      <category>unifox</category>
      <author>mo1lusca</author>
      <guid isPermaLink="true">https://mollusca0907.tistory.com/60</guid>
      <comments>https://mollusca0907.tistory.com/60#entry60comment</comments>
      <pubDate>Sun, 2 Nov 2025 03:36:03 +0900</pubDate>
    </item>
    <item>
      <title>[백준] 13510 트리와 쿼리 1 - C++</title>
      <link>https://mollusca0907.tistory.com/57</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/13510&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/13510&lt;/a&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;트리상의 임의의 두 노드 u, v를 잇는 경로 중 비용이 가장 큰 간선의 비용을 출력하면 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;HLD (Heavy Light Decomposition) 를 사용하면 된다!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; 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;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;부분 코드 설명&lt;/h3&gt;
&lt;pre id=&quot;code_1761238536936&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;bits/stdc++.h&amp;gt;
using namespace std;

const int MAX = 100'005;

int sz[MAX];
int dep[MAX];
int par[MAX];
int top[MAX];
int in[MAX], out[MAX]; 

vector&amp;lt;int&amp;gt; g[MAX];
vector&amp;lt;pair&amp;lt;pair&amp;lt;int, int&amp;gt;, int&amp;gt;&amp;gt; edge;

int seg[MAX * 4 + 16];&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선언부이다.. 와우...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐가 많아보이지만 사실 간단하다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- sz[i]는 노드 i를 루트로 삼는 서브트리의 크기이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- dep[i]는 노드 i의 깊이이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- par[i]는 노드 i의 부모 노드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- top[i]는 노드 i가 속한 체인의 최상단 노드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- in[], out[]은 ETT처럼 방문시간을 기록한 것이다.&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;- edge는 간선 정보를 저장한다. (문제 조건이다.) {{u,v},w}의 구조로 노드 두개, 비용을 저장한다,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- seg는 세그트리다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761238989856&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void dfs1(int v = 1, int p = 0) {
	par[v] = p;
	sz[v] = 1;
	for (auto&amp;amp; i : g[v]) {
		if (p == i) {
			continue;
		}

		dep[i] = dep[v] + 1;
		par[i] = v;

		dfs1(i, v);

		sz[v] += sz[i];
		if (g[v][0] == p || sz[i] &amp;gt; sz[g[v][0]]) {
			swap(i, g[v][0]);
		}
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 DFS 함수이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;par, sz, dep 배열을 채우고, &lt;b&gt;서브트리가 가장 큰 자식을 맨 앞&lt;/b&gt;으로 보낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로 인해 dfs1 함수가 끝나면, 노드 v에서 뻗어나가는 heavy edge는 &lt;b&gt;( v, g[v][0] )&lt;/b&gt; 이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761275440103&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int cnt = 0;
void dfs2(int v = 1, int p = 0) {
	in[v] = ++cnt;
	for (auto i : g[v]) {
		if (p == i) {
			continue;
		}
		top[i] = (i == g[v][0] ? top[v] : i); //&amp;lt;1&amp;gt;
		dfs2(i, v);
	}
	out[v] = cnt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째 DFS 함수이다. in과 out, top을 채운다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;top은 아까 말했듯 해당 노드가 속한 체인의 최상단에 있는 정점을 나타낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lt;1&amp;gt;&lt;/b&gt; : 노드 v의 자식 노드 i의 top을 채운다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- i가 g[v][0]이라면, 즉 &lt;b&gt;i가 heavy child라면 v의 top을 물려받는다.&lt;/b&gt; (같은 heavy chain에 속하기 때문에)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- i가 g[v][0]이 아니라면, i는 v가 속한 heavy chain에 포함되지 않기 때문에, &lt;b&gt;새로운 체인을 시작&lt;/b&gt;해준다 (최상단이 자기 자신)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761276580321&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void update(int idx, int left, int right, int cidx, int cval) {
	if (cidx &amp;lt; left || right &amp;lt; cidx) {
		return;
	}
	if (left == right) {
		seg[idx] = cval;
		return;
	}
	int mid = (left + right) / 2;
	update(idx * 2, left, mid, cidx, cval);
	update(idx * 2 + 1, mid + 1, right, cidx, cval);
	seg[idx] = max(seg[idx * 2], seg[idx * 2 + 1]);
	return;
}
int s_query(int idx, int left, int right, int qleft, int qright) {
	if (qright &amp;lt; left || right &amp;lt; qleft) {
		return 0;
	}
	if (qleft &amp;lt;= left &amp;amp;&amp;amp; right &amp;lt;= qright) {
		return seg[idx];
	}
	int mid = (left + right) / 2;
	return max(s_query(idx * 2, left, mid, qleft, qright), s_query(idx * 2 + 1, mid + 1, right, qleft, qright));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;점업뎃 구간쿼리 맥스세그를 구현하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리를 수행하는 함수명이 query가 아니라 s_query인 이유는... 쿼리를 수행하려면 한 단계가 더 필요하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761285009172&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int query(int u, int v) {
	int res = 0;
	while (top[u] != top[v]) { //&amp;lt;1&amp;gt;
		if (dep[top[u]] &amp;lt; dep[top[v]]) { //&amp;lt;2&amp;gt;
			swap(u, v);
		}
		int st = top[u]; //&amp;lt;3&amp;gt;
		res = max(res, s_query(1, 1, n, in[st], in[u])); //&amp;lt;4&amp;gt;
		u = par[st]; //&amp;lt;5&amp;gt;
	}
	if (dep[u] &amp;gt; dep[v]) { //&amp;lt;6-0&amp;gt;
		swap(u, v);
	}
	res = max(res, s_query(1, 1, n, in[u] + 1, in[v])); //&amp;lt;6&amp;gt;
	return res;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HLD에 기반한 경로쿼리 함수이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lt;1&amp;gt;&lt;/b&gt; : 노드 u와 v가 같은 heavy chain에 속할 때 까지 반복한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lt;2&amp;gt;&lt;/b&gt; : top[u]의 depth가 항상 top[v]의 depth보다 같거나 크도록 만든다. depth가 큰 쪽부터 쿼리를 처리하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- u가 속한 체인이 지금 처리할 체인이라는 뜻이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lt;3&amp;gt;&lt;/b&gt; : st는 u가 속한 체인의 최상단 노드를 뜻한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lt;4&amp;gt;&lt;/b&gt; : st ~ u 구간에 쿼리를 날린다. 이로서 &lt;b&gt;u가 속했던 heavy chain에서의 최댓값&lt;/b&gt;을 찾을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-- in[st] ~ in[u] 구간 내의 모든 노드가 하나의 heavy chain에 속한다는 것을 어떻게 알 수 있을까?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;in은 그저 dfs2가 방문한 순서이니 heavy chain에 속하지 않은 노드도 포함되는 것 아닐까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;하지만 우리는 dfs1에서 &lt;b&gt;heavy child를 맨 앞&lt;/b&gt;( g[v][0] ) 에 오게 해두었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;이것이 dfs2에서 g[v]를 순회하며 탐색할 때, &lt;b&gt;항상 heavy child인 노드를 먼저 탐색&lt;/b&gt;하니&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;결과적으로 heavy chain을 연속적으로 탐색할 수 있게 해주는것이다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;따라서 &lt;b&gt;같은 heavy chain에 속한 노드는 in 내에서 연속적인 구간&lt;/b&gt;을 갖는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lt;5&amp;gt;&lt;/b&gt; : u가 속한 체인의 최상단 노드의 부모를 u에 저장한다. &amp;lt;4&amp;gt;에서 쿼리를 처리했으니 상위 체인으로 커서를 옮기는 느낌이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lt;6&amp;gt;&lt;/b&gt; : 이 코드는 &amp;lt;1&amp;gt;의 while문이 종료되었을 때에 실행된다. 그 말은 즉 현재 u와 v가 같은 heavy chain 내에 있고, 이외의 구간에 대한 처리는 모두 &amp;lt;4&amp;gt;를 통해 처리했다는 의미이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한마디로, 현재 &lt;b&gt;u와 v가 같은 체인&lt;/b&gt;에 있으니 마지막으로 &lt;b&gt;u~v 구간에 대해 쿼리&lt;/b&gt;를 날려주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- in[u] ~ in[v] 구간이 아니라 in[u]+1 ~ in[v] 구간인데에는 이유가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 문제상 노드가 아니라 간선에 대해 쿼리를 수행하는 것이기 때문에 HLD를 쓰기 위해서 간선을 고유한 노드에 매핑할 필요가 있다.&lt;/p&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; 이때 &amp;lt;6-0&amp;gt;에 의해 u~v중 depth가 제일 낮은 (루트와 제일 가까운) 노드인 u는 u의 부모로 향하는 간선을 나타내게 되고, 이는 u~v의 구간에서 벗어난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 따라서 in[u]+1을 해줌으로 u~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;아무튼 이런 절차를 거쳐 얻은 res를 return해준다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main함수는 특별한게 딱히 없으므로 자세한 설명은 생략한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;전체 코드&lt;/h3&gt;
&lt;pre id=&quot;code_1761292578083&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;bits/stdc++.h&amp;gt;
using namespace std;
const int MAX = 100'005;

int sz[MAX]; //i를 루트로 하는 서브트리 크기
int dep[MAX]; //i의 깊이
int par[MAX]; //i의 부모 노드
int top[MAX]; //i가 속한 체인의 최상단 노드
int in[MAX], out[MAX]; //DFS ordering
vector&amp;lt;int&amp;gt; g[MAX]; // graph
vector&amp;lt;pair&amp;lt;pair&amp;lt;int, int&amp;gt;, int&amp;gt;&amp;gt; edge;

int seg[MAX * 4 + 16];

int n;

void dfs1(int v = 1, int p = 0) {
	par[v] = p;
	sz[v] = 1;
	for (auto&amp;amp; i : g[v]) {
		if (p == i) {
			continue;
		}
		dep[i] = dep[v] + 1;
		par[i] = v;
		dfs1(i, v);
		sz[v] += sz[i];
		if (g[v][0] == p || sz[i] &amp;gt; sz[g[v][0]]) {
			swap(i, g[v][0]);
		}
	}
}
int cnt = 0;
void dfs2(int v = 1, int p = 0) {
	in[v] = ++cnt;
	for (auto i : g[v]) {
		if (p == i) {
			continue;
		}
		top[i] = (i == g[v][0] ? top[v] : i);
		dfs2(i, v);
	}
	out[v] = cnt;
}

void update(int idx, int left, int right, int cidx, int cval) {
	if (cidx &amp;lt; left || right &amp;lt; cidx) {
		return;
	}
	if (left == right) {
		seg[idx] = cval;
		return;
	}
	int mid = (left + right) / 2;
	update(idx * 2, left, mid, cidx, cval);
	update(idx * 2 + 1, mid + 1, right, cidx, cval);
	seg[idx] = max(seg[idx * 2], seg[idx * 2 + 1]);
	return;
}

int s_query(int idx, int left, int right, int qleft, int qright) {
	if (qright &amp;lt; left || right &amp;lt; qleft) {
		return 0;
	}
	if (qleft &amp;lt;= left &amp;amp;&amp;amp; right &amp;lt;= qright) {
		return seg[idx];
	}
	int mid = (left + right) / 2;
	return max(s_query(idx * 2, left, mid, qleft, qright), s_query(idx * 2 + 1, mid + 1, right, qleft, qright));
}

int query(int u, int v) {
	int res = 0;
	while (top[u] != top[v]) {
		if (dep[top[u]] &amp;lt; dep[top[v]]) {
			swap(u, v);
		}
		int st = top[u];
		res = max(res, s_query(1, 1, n, in[st], in[u]));
		u = par[st];
	}
	if (dep[u] &amp;gt; dep[v]) {
		swap(u, v);
	}
	res = max(res, s_query(1, 1, n, in[u] + 1, in[v]));
	return res;
}
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);

	int m;
	edge.push_back({ {-1,-1},-1 });
	cin &amp;gt;&amp;gt; n;
	for (int i = 1; i &amp;lt;= n - 1; i++) {
		int u, v, w;
		cin &amp;gt;&amp;gt; u &amp;gt;&amp;gt; v &amp;gt;&amp;gt; w;
		edge.push_back({ {u,v},w });
		g[u].push_back(v);
		g[v].push_back(u);
	}
	dep[1] = 0;
	par[1] = 0;
	top[1] = 1;
	dfs1();
	dfs2();
	for (int i = 1; i &amp;lt;= n - 1; i++) {
		int u = edge[i].first.first;
		int v = edge[i].first.second;
		int w = edge[i].second;
		if (dep[u] &amp;lt; dep[v]) {
			swap(u, v);
		}
		update(1, 1, n, in[u], w);
	}
	cin &amp;gt;&amp;gt; m;
	for (int i = 1; i &amp;lt;= m; i++) {
		int command, a, b;
		cin &amp;gt;&amp;gt; command &amp;gt;&amp;gt; a &amp;gt;&amp;gt; b;
		if (command == 1) {
			int u = edge[a].first.first;
			int v = edge[a].first.second;
			if (dep[u] &amp;lt; dep[v]) {
				swap(u, v);
			}
			update(1, 1, n, in[u], b);
		}
		else {
			cout &amp;lt;&amp;lt; query(a, b) &amp;lt;&amp;lt; &quot;\n&quot;;
		}
	}
	return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; 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;</description>
      <category>PS</category>
      <category>C++</category>
      <category>백준</category>
      <author>mo1lusca</author>
      <guid isPermaLink="true">https://mollusca0907.tistory.com/57</guid>
      <comments>https://mollusca0907.tistory.com/57#entry57comment</comments>
      <pubDate>Fri, 24 Oct 2025 17:00:07 +0900</pubDate>
    </item>
    <item>
      <title>[백준] 18227 성대나라의 물탱크 - C++</title>
      <link>https://mollusca0907.tistory.com/56</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/18227&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/18227&lt;/a&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;루트(수도)와 임의의 노드 사이의 경로에 있는 노드들에,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;루트부터 +1, +2, +3...이 더해진다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 경로라고 생각하면? 뭔가 HLD + Lazy prop을 써야할 것 같지만&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;ETT만으로도 풀 수 있다!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;핵심 아이디어는 노드 당 &lt;b&gt;물이 부어진 횟수&lt;/b&gt;와,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;물을 한번 부을때마다 &lt;b&gt;얼마나 부어질지&lt;/b&gt;를 따로 생각하는 것이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;다운로드.png&quot; data-origin-width=&quot;596&quot; data-origin-height=&quot;510&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pkw4V/dJMb9MJFkJk/ktyWpqnONKCMj9HOrhA980/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pkw4V/dJMb9MJFkJk/ktyWpqnONKCMj9HOrhA980/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pkw4V/dJMb9MJFkJk/ktyWpqnONKCMj9HOrhA980/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpkw4V%2FdJMb9MJFkJk%2FktyWpqnONKCMj9HOrhA980%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;257&quot; data-filename=&quot;다운로드.png&quot; data-origin-width=&quot;596&quot; data-origin-height=&quot;510&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이러한 트리가 있다고 해보자.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;6번 노드에 물을 부으려면 1 - 4 - 6 순서로 1, 2, 3 만큼의 물을 부어야 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;항상 루트를 첫번째로, 목표 노드를 마지막으로 물을 붓게 되고..&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 경로에 포함되는 &lt;b&gt;노드에 부어지는 물의 양&lt;/b&gt;은 루트로부터의 거리, 즉 &lt;b&gt;depth&lt;/b&gt;임을 알 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;노드 별로 &lt;b&gt;항상 같은 양만큼 물이 부어지니&lt;/b&gt;, 노드 당 물이 부어진 횟수만 알면 임의의 노드에 담긴 물의 총량을 알 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;위의 사진의 트리에서 4번 노드에 물이 부어지는 경우는&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;4번 노드에 직접 물이 부어지는 경우, 6번이나 7번 노드에 물이 부어지는 경우가 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 점에서 우리는 어떤 노드에 물이 부어지는 횟수는 해당 노드의 자식노드의 방문횟수 + 해당노드의 방문횟수 임을 알 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;한마디로,&lt;b&gt; 물이 부어지는 횟수&lt;/b&gt;는 해당 노드를 루트로 갖는 &lt;b&gt;서브트리의 모든 노드의 방문횟수&lt;/b&gt;이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;방문횟수를 구하는 합세그를 만들고, ETT로 서브트리 쿼리를 돌리면 될 것 같다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;부분 코드 설명&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1761193646056&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;bits/stdc++.h&amp;gt;
using namespace std;
using ll = long long;

const int MAX = 200'005;

vector&amp;lt;ll&amp;gt; in(MAX);
vector&amp;lt;ll&amp;gt; out(MAX);
vector&amp;lt;ll&amp;gt; depth(MAX);

ll seg[MAX * 4 + 16];

vector&amp;lt;int&amp;gt; g[MAX];
vector&amp;lt;bool&amp;gt; visited(MAX, 0);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;선언부이다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;ETT에 사용할 in과 out, 노드의 깊이를 나타낼 depth를 만들어준다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;구간합을 위한 세그트리를 만들고, 원본 트리를 나타낼 g(graph), dfs 중 방문상태를 저장할 visited를 만든다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761193855407&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int cnt = 0;
void dfs(int cur, int dep) {
	in[cur] = ++cnt;
	depth[cur] = dep;
	visited[cur] = 1;
	for (auto&amp;amp; next : g[cur]) {
		if (visited[next] != 1) {
			dfs(next, dep + 1);
		}
	}
	out[cur] = cnt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ETT를 위해 dfs를 수행하는 함수이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://mollusca0907.tistory.com/55&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2025.10.22 - [백준] - [백준] 14268 회사 문화 2 - C++&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글의 구현과 비슷하지만, depth를 구하는 부분이 추가되었고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제에서 그래프를 양방향으로 주기 때문에 visited를 체크하는 부분이 추가되었다.&lt;/p&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;pre id=&quot;code_1761193954569&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void update(ll idx, ll left, ll right, ll cidx, ll cval) {
	if (cidx &amp;lt; left || right &amp;lt; cidx) {
		return;
	}
	if (left == right) {
		seg[idx] += cval;
		return;
	}
	ll mid = (left + right) / 2;
	update(idx * 2, left, mid, cidx, cval);
	update(idx * 2 + 1, mid + 1, right, cidx, cval);
	seg[idx] = seg[idx * 2] + seg[idx * 2 + 1];
	return;
}
ll query(ll idx, ll left, ll right, ll qleft, ll qright) {
	if (qright &amp;lt; left || right &amp;lt; qleft) {
		return 0;
	}
	if (qleft &amp;lt;= left &amp;amp;&amp;amp; right &amp;lt;= qright) {
		return seg[idx];
	}
	ll mid = (left + right) / 2;
	return query(idx * 2, left, mid, qleft, qright) + query(idx * 2 + 1, mid + 1, right, qleft, qright);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1761199495855&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);

	int n, m, r;
	cin &amp;gt;&amp;gt; n &amp;gt;&amp;gt; r;
	for (int i = 1; i &amp;lt; n; i++) {
		int u, v;
		cin &amp;gt;&amp;gt; u &amp;gt;&amp;gt; v;
		g[u].push_back(v);
		g[v].push_back(u);
	}

	dfs(r, 1); //&amp;lt;1&amp;gt;

	cin &amp;gt;&amp;gt; m;
	for (int i = 0; i &amp;lt; m; i++) {
		int command, a;
		cin &amp;gt;&amp;gt; command &amp;gt;&amp;gt; a;
		if (command == 1) {
			update(1, 1, n, in[a], 1); //&amp;lt;2&amp;gt;
		}
		else {
			cout &amp;lt;&amp;lt; query(1, 1, n, in[a], out[a]) * depth[a] &amp;lt;&amp;lt; &quot;\n&quot;; //&amp;lt;3&amp;gt;
		}
	}
	return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main이다. 딱히 특별한게 엄청 있진 않지만..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lt;1&amp;gt;&lt;/b&gt; : ETT를 돌린다! 입력으로 받은 수도를 루트로 돌려주어야 하기 때문에 인자를 잘 넣어주도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lt;2&amp;gt;&lt;/b&gt; : 위에서 말했듯 경로에 포함되는 노드의 방문횟수는 구간합으로 구해지기에 경로의 끝 노드에만 +1 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lt;3&amp;gt;&lt;/b&gt; : 이것도 위에서 말했듯, 임의의 노드에 담겨있는 물은 (방문횟수)*(depth) 이기 때문에,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브트리의 구간합으로 방문횟수를 구해주고, dfs를 돌리며 만들어 둔 depth배열에서 값을 꺼내와 곱해주면 된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;전체 코드&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1761199845448&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;bits/stdc++.h&amp;gt;
using namespace std;
using ll = long long;

const int MAX = 200'005;

vector&amp;lt;ll&amp;gt; in(MAX);
vector&amp;lt;ll&amp;gt; out(MAX);

vector&amp;lt;ll&amp;gt; depth(MAX);

ll seg[MAX * 4 + 16];

vector&amp;lt;int&amp;gt; g[MAX];
vector&amp;lt;bool&amp;gt; visited(MAX, 0);

int cnt = 0;
void dfs(int cur, int dep) {
	in[cur] = ++cnt;
	depth[cur] = dep;
	visited[cur] = 1;
	for (auto&amp;amp; next : g[cur]) {
		if (visited[next] != 1) {
			dfs(next, dep + 1);
		}
	}
	out[cur] = cnt;
}

void update(ll idx, ll left, ll right, ll cidx, ll cval) {
	if (cidx &amp;lt; left || right &amp;lt; cidx) {
		return;
	}
	if (left == right) {
		seg[idx] += cval;
		return;
	}
	ll mid = (left + right) / 2;
	update(idx * 2, left, mid, cidx, cval);
	update(idx * 2 + 1, mid + 1, right, cidx, cval);
	seg[idx] = seg[idx * 2] + seg[idx * 2 + 1];
	return;
}
ll query(ll idx, ll left, ll right, ll qleft, ll qright) {
	if (qright &amp;lt; left || right &amp;lt; qleft) {
		return 0;
	}
	if (qleft &amp;lt;= left &amp;amp;&amp;amp; right &amp;lt;= qright) {
		return seg[idx];
	}
	ll mid = (left + right) / 2;
	return query(idx * 2, left, mid, qleft, qright) + query(idx * 2 + 1, mid + 1, right, qleft, qright);
}

int main() {

	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);

	int n, m, r;
	cin &amp;gt;&amp;gt; n &amp;gt;&amp;gt; r;
	for (int i = 1; i &amp;lt; n; i++) {
		int u, v;
		cin &amp;gt;&amp;gt; u &amp;gt;&amp;gt; v;
		g[u].push_back(v);
		g[v].push_back(u);
	}

	dfs(r, 1);

	cin &amp;gt;&amp;gt; m;
	for (int i = 0; i &amp;lt; m; i++) {
		int command, a;
		cin &amp;gt;&amp;gt; command &amp;gt;&amp;gt; a;
		if (command == 1) {
			update(1, 1, n, in[a], 1);
		}
		else {
			cout &amp;lt;&amp;lt; query(1, 1, n, in[a], out[a]) * depth[a] &amp;lt;&amp;lt; &quot;\n&quot;;
		}
	}
	return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; 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;</description>
      <category>PS</category>
      <category>C++</category>
      <category>백준</category>
      <author>mo1lusca</author>
      <guid isPermaLink="true">https://mollusca0907.tistory.com/56</guid>
      <comments>https://mollusca0907.tistory.com/56#entry56comment</comments>
      <pubDate>Thu, 23 Oct 2025 15:11:59 +0900</pubDate>
    </item>
    <item>
      <title>[백준] 14268 회사 문화 2 - C++</title>
      <link>https://mollusca0907.tistory.com/55</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/14268&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/14268&lt;/a&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;얼핏 보면 간단히 합세그만 구현해도 되는게 아닌가? 싶지만&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;문제에서 주는 조직도는 결국 트리 형태이고, 쿼리 또한 서브트리를 대상으로 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 세그트리는 선형 구조(배열)에서 사용할 수 있기 때문에 무지성 합세그구현은 못한다..&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이럴땐 트리를 선형으로 펴주는 ETT를 사용하면 된다!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;ETT(Euler Tour Technique) 란?&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;ETT란, 임의의 노드의 서브트리를 선형구조의 구간으로 표현할수 있게 하는 알고리즘(테크닉)이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;세그트리와 함께 사용하면 서브트리의 합을 구할 수 있는 것이다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;497&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/t8WYm/dJMb9OgpAqI/Kc47F1Ic5qsmCXmXKlWORK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/t8WYm/dJMb9OgpAqI/Kc47F1Ic5qsmCXmXKlWORK/img.png&quot; data-alt=&quot;출처 - kokodak tistory&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/t8WYm/dJMb9OgpAqI/Kc47F1Ic5qsmCXmXKlWORK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ft8WYm%2FdJMb9OgpAqI%2FKc47F1Ic5qsmCXmXKlWORK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;512&quot; height=&quot;497&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;497&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 - kokodak tistory&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;(1번 노드가 루트 노드라고 생각하자.)&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;ETT는 기본적으로 DFS가 노드를 방문하는 순서에 따라 번호를 매긴다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;DFS를 수행하는 과정에서 직접 노드를 방문하는 가상의 커서가 있다고 생각해보자.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이때 가상의 커서가 &lt;b&gt;i번째 노드에 최초로 진입&lt;/b&gt;한 시점을 &lt;b&gt;in[i]&lt;/b&gt;이라고 하자.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;또한 i번째 노드의 &lt;b&gt;모든 자식의 탐색이 완료된 시점&lt;/b&gt;을 &lt;b&gt;out[i]&lt;/b&gt;라고 하자.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;위 그림에서 노드 당 &lt;b&gt;in&lt;/b&gt;은 &lt;b&gt;검은 글씨&lt;/b&gt;, &lt;b&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;out&lt;/span&gt;&lt;/b&gt;은 &lt;b&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;파란 글씨&lt;/span&gt;&lt;/b&gt;로 표현되어있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 방문 순서를 in과 out으로 표현하면 뭐가 좋느냐??&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;임의의 노드 i가 있을때, i를 루트로 삼는 서브트리의 모든 노드 j는&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;in[i] &amp;lt; in[j], out[j] &amp;lt;= out[i]를 만족한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;한마디로 &lt;b&gt;in[i] ~ out[i] 구간&lt;/b&gt;에는 i를 루트로 삼는 &lt;b&gt;서브트리의 모든 노드가 포함&lt;/b&gt;된다!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 비선형인 트리구조를 in과 out을 통해 선형구조처럼 만들었으니 이제 세그트리를 사용할 수 있다!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;부분 코드 설명&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1761099010938&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;bits/stdc++.h&amp;gt;
using namespace std;
using ll = long long;

vector&amp;lt;ll&amp;gt; in(100'005);
vector&amp;lt;ll&amp;gt; out(100'005);

ll seg[100'000 * 4 + 16];
ll lazy[100'000 * 4 + 16];

vector&amp;lt;int&amp;gt; g[100'005];&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;선언부이다.. 문제조건에서 n이 100'000이라 했으니 약간 널널하게 배열 사이즈를 잡았다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;ETT를 위한 in과 out도 선언하고&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;서브트리를 대상으로 구간 업데이트도 해야하기 때문에 lazy prop용 배열도 만들어준다...&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;g(graph)는 조직도를 나타내는 원본 트리이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761099249204&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int cnt = 0;
void dfs(int cur) {
	in[cur] = ++cnt; //&amp;lt;1&amp;gt;
	for (auto&amp;amp; next : g[cur]) { //&amp;lt;2&amp;gt;
		dfs(next);
	}
	out[cur] = cnt; //&amp;lt;3&amp;gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;ETT를 진행하는 DFS 함수이다. 전역변수 cnt를 사용해 방문순서를 기록한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lt;1&amp;gt;&lt;/b&gt; : cnt를 1 증가시키고, 그 값을 in에 저장한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lt;2&amp;gt;&lt;/b&gt; : 그냥 조직도 훑으면서 재귀 DFS 돌린다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lt;3&amp;gt;&lt;/b&gt; : 해당 노드의 서브트리의 탐색이 종료된 시점에 실행된다. out을 cnt값으로 저장한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;탐색이 완료된 시점을 저장하는것이기에 cnt를 증가시키지는 않는다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;세그트리는 그냥 lazy 합세그로 구현하면 된다.&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;pre id=&quot;code_1761099625693&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void push(ll idx, ll left, ll right) {
	if (lazy[idx] != 0) {
		seg[idx] += lazy[idx] * (right - left + 1);
		if (left != right) {
			lazy[idx * 2] += lazy[idx];
			lazy[idx * 2 + 1] += lazy[idx];
		}
		lazy[idx] = 0;
	}
}
void update(ll idx, ll left, ll right, ll cleft, ll cright, ll cval) {
	push(idx, left, right);
	if (cright &amp;lt; left || right &amp;lt; cleft) {
		return;
	}
	if (cleft &amp;lt;= left &amp;amp;&amp;amp; right &amp;lt;= cright) {
		lazy[idx] += cval;
		push(idx, left, right);
		return;
	}
	ll mid = (left + right) / 2;
	update(idx * 2, left, mid, cleft, cright, cval);
	update(idx * 2 + 1, mid + 1, right, cleft, cright, cval);
	seg[idx] = seg[idx * 2] + seg[idx * 2 + 1];
	return;
}
ll query(ll idx, ll left, ll right, ll qidx) {
	push(idx, left, right);
	if (qidx &amp;lt; left || right &amp;lt; qidx) {
		return 0;
	}
	if (left == right) {
		return seg[idx];
	}
	ll mid = (left + right) / 2;
	return query(idx * 2, left, mid, qidx) + query(idx * 2 + 1, mid + 1, right, qidx);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761099702719&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);

	int n, m;
	cin &amp;gt;&amp;gt; n &amp;gt;&amp;gt; m;
	for (int i = 1; i &amp;lt;= n; i++) { //&amp;lt;1&amp;gt;
		int x;
		cin &amp;gt;&amp;gt; x;
		if (x != -1) g[x].push_back(i);
	}

	dfs(1); //&amp;lt;2&amp;gt;

	for (int i = 0; i &amp;lt; m; i++) {
		int command, a, b;
		cin &amp;gt;&amp;gt; command;
		if (command == 1) {
			cin &amp;gt;&amp;gt; a &amp;gt;&amp;gt; b;
			update(1, 1, n, in[a], out[a], b); //&amp;lt;3&amp;gt;
		}
		else {
			cin &amp;gt;&amp;gt; a;
			cout &amp;lt;&amp;lt; query(1, 1, n, in[a]) &amp;lt;&amp;lt; &quot;\n&quot;; //&amp;lt;4&amp;gt;
		}
	}
	return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;main이다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;lt;1&amp;gt; : 조직도를 입력받는다. i번째 노드의 부모 노드 x가 주어지고, 1번노드는 -1이 주어진다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 점에 유의하며 코드를 적자.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;lt;2&amp;gt; : ETT용 DFS를 돌린다. 루트노드의 번호인 1을 넣어주면 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;lt;3&amp;gt; : a번째 직원이 b만큼 칭찬을 받으면 a번째 노드를 루트로 하는 서브트리의 모든 노드가&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;b만큼 칭찬을 받게된다. 이 말은 즉슨 아까 말했듯 &lt;b&gt;in[a] ~ out[a] 구간에 b만큼 더해주면&lt;/b&gt; 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;lt;4&amp;gt; : a번째 노드의 값을 출력하는 쿼리이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;트리에서의 a번째 노드가 선형구조에서는 &lt;b&gt;in[a]를 인덱스&lt;/b&gt;로 가지므로&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;in[a]의 값을 쿼리로 날리면 된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;전체 코드&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1761101315635&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;bits/stdc++.h&amp;gt;
using namespace std;
using ll = long long;

vector&amp;lt;ll&amp;gt; in(100'005);
vector&amp;lt;ll&amp;gt; out(100'005);

ll seg[100'000 * 4 + 16];
ll lazy[100'000 * 4 + 16];

vector&amp;lt;int&amp;gt; g[100'005];

int cnt = 0;
void dfs(int cur) {
	in[cur] = ++cnt;
	for (auto&amp;amp; next : g[cur]) {
		dfs(next);
	}
	out[cur] = cnt;
}
void push(ll idx, ll left, ll right) {
	if (lazy[idx] != 0) {
		seg[idx] += lazy[idx] * (right - left + 1);
		if (left != right) {
			lazy[idx * 2] += lazy[idx];
			lazy[idx * 2 + 1] += lazy[idx];
		}
		lazy[idx] = 0;
	}
}
void update(ll idx, ll left, ll right, ll cleft, ll cright, ll cval) {
	push(idx, left, right);
	if (cright &amp;lt; left || right &amp;lt; cleft) {
		return;
	}
	if (cleft &amp;lt;= left &amp;amp;&amp;amp; right &amp;lt;= cright) {
		lazy[idx] += cval;
		push(idx, left, right);
		return;
	}
	ll mid = (left + right) / 2;
	update(idx * 2, left, mid, cleft, cright, cval);
	update(idx * 2 + 1, mid + 1, right, cleft, cright, cval);
	seg[idx] = seg[idx * 2] + seg[idx * 2 + 1];
	return;
}
ll query(ll idx, ll left, ll right, ll qidx) {
	push(idx, left, right);
	if (qidx &amp;lt; left || right &amp;lt; qidx) {
		return 0;
	}
	if (left == right) {
		return seg[idx];
	}
	ll mid = (left + right) / 2;
	return query(idx * 2, left, mid, qidx) + query(idx * 2 + 1, mid + 1, right, qidx);
}
int main() {

	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);

	int n, m;
	cin &amp;gt;&amp;gt; n &amp;gt;&amp;gt; m;
	for (int i = 1; i &amp;lt;= n; i++) {
		int x;
		cin &amp;gt;&amp;gt; x;
		if (x != -1) g[x].push_back(i);
	}

	dfs(1);

	for (int i = 0; i &amp;lt; m; i++) {
		int command, a, b;
		cin &amp;gt;&amp;gt; command;
		if (command == 1) {
			cin &amp;gt;&amp;gt; a &amp;gt;&amp;gt; b;
			update(1, 1, n, in[a], out[a], b);
		}
		else {
			cin &amp;gt;&amp;gt; a;
			cout &amp;lt;&amp;lt; query(1, 1, n, in[a]) &amp;lt;&amp;lt; &quot;\n&quot;;
		}
	}
	return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;ETT.. 정말 간단하지만 재밌는 아이디어 같다..&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;</description>
      <category>PS</category>
      <category>C++</category>
      <category>백준</category>
      <author>mo1lusca</author>
      <guid isPermaLink="true">https://mollusca0907.tistory.com/55</guid>
      <comments>https://mollusca0907.tistory.com/55#entry55comment</comments>
      <pubDate>Wed, 22 Oct 2025 11:49:25 +0900</pubDate>
    </item>
  </channel>
</rss>