<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>doorbals_512.log</title>
        <link>https://velog.io/</link>
        <description>게임 클라이언트 개발자 지망생의 TIL</description>
        <lastBuildDate>Thu, 24 Aug 2023 13:24:51 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>doorbals_512.log</title>
            <url>https://velog.velcdn.com/images/doorbals_512/profile/285d5d20-72c8-4b14-9852-308e9949b95d/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. doorbals_512.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/doorbals_512" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Unreal]언리얼 엔진 멀티 플레이어 시스템 Part2]]></title>
            <link>https://velog.io/@doorbals_512/Unreal%EC%96%B8%EB%A6%AC%EC%96%BC-%EC%97%94%EC%A7%84-%EB%A9%80%ED%8B%B0-%ED%94%8C%EB%A0%88%EC%9D%B4%EC%96%B4-%EC%8B%9C%EC%8A%A4%ED%85%9C-Part2</link>
            <guid>https://velog.io/@doorbals_512/Unreal%EC%96%B8%EB%A6%AC%EC%96%BC-%EC%97%94%EC%A7%84-%EB%A9%80%ED%8B%B0-%ED%94%8C%EB%A0%88%EC%9D%B4%EC%96%B4-%EC%8B%9C%EC%8A%A4%ED%85%9C-Part2</guid>
            <pubDate>Thu, 24 Aug 2023 13:24:51 GMT</pubDate>
            <description><![CDATA[<h2 id="rpcs-in-detail">RPCs in Detail</h2>
<h3 id="1-rpc의-종류">1) RPC의 종류</h3>
<ul>
<li>정기적이고 대역폭이 제한된 네트워크 업데이트 프로세스의 경우 <strong>Property Replication</strong>에 적용되고, 네트워크를 통해 즉시 전송하려는 우선순위가 높은 메시지의 경우 <strong>RPC</strong>를 사용하여 전송하게 된다.</li>
<li>모든 UFUNCTION은 <strong>Client, Server, Multicast</strong> 중 하나로 지정하여 RPC로 만들 수 있다.
  <img src="https://velog.velcdn.com/images/doorbals_512/post/bbb75040-856e-4cc8-bb74-705dd6cde21f/image.png" alt=""></li>
<li>서버에서 <strong>Client RPC</strong>를 호출하면 액터를 소유한 클라이언트에서 Implementation 함수가 실행된다.
  <img src="https://velog.velcdn.com/images/doorbals_512/post/65a75ba4-999d-46d6-b8aa-6f02eccae788/image.png" alt=""></li>
<li>액터를 소유한 클라이언트에서 <strong>Server RPC</strong>를 호출하면 Implementation 함수가 서버에서 실행된다.
  <img src="https://velog.velcdn.com/images/doorbals_512/post/79a115db-4fdf-467e-8390-510038aa4dde/image.png" alt=""></li>
<li>서버에서 <strong>Multicast RPC</strong>를 호출하면 Implementation 함수가 서버 및 모든 클라이언트에서 실행된다. 
  <img src="https://velog.velcdn.com/images/doorbals_512/post/50039a55-54d8-42a9-afca-b7fbd61fe582/image.png" alt=""><ul>
<li>Server 및 Client RPC와 달리 관련성은 Multicast RPC의 요소이다. 액터를 소유하지 않은 클라이언트는 액터에 대한 채널을 가지고 있지 않을 수도 있기 때문이다.
<img src="https://velog.velcdn.com/images/doorbals_512/post/db76c5b4-348c-4eb0-8f52-c6624f6f4900/image.png" alt=""></li>
<li>위와 같은 경우 클라이언트는 단순히 RPC를 수신하지 않는다. 이는 지속적인 상태 변경을 클라이언트에 복제하기 위한 수단으로 Multicast RPC에 의존해서는 안 된다는 것을 의미한다.
<img src="https://velog.velcdn.com/images/doorbals_512/post/542fd9ec-4bcf-4037-adde-a8fafd656e95/image.png" alt=""></li>
</ul>
</li>
</ul>
<h3 id="2-reliability-신뢰성">2) Reliability (신뢰성)</h3>
<ul>
<li>RPC는 <strong>Reliable(신뢰 가능)</strong> 또는 <strong>Unreliable(신뢰 불가능)</strong>로 설정할 수 있다. 
  <img src="https://velog.velcdn.com/images/doorbals_512/post/6f989916-b059-4246-a936-99d512702795/image.png" alt=""><ul>
<li>대역폭이 포화된 경우 Unreliable RPC가 삭제될 수 있다. 이 경우 도착이 보장되지 않으므로 순서대로 도착한다는 보장도 없다.</li>
<li>Reliable RPC는 도착하는 것이 보장되며, 단일 액터 내에서는 신뢰할 수 있는 RPC가 호출된 순서대로 도착하는 것이 보장된다.</li>
</ul>
</li>
<li>함수 호출이 게임 플레이에 중요한 경우 이러한 안전성이 필요하지만, 과도하게 사용하면 대역폭 포화가 악화되고 패킷 손실 시 병목 현상이 발생할 수 있다.</li>
</ul>
<h3 id="3-implementation">3) Implementation</h3>
<ul>
<li>C++에서는 함수의 실제 본문을 <strong><code>_Implementation</code></strong> 접미사로 정의해야 한다.<ul>
<li>이는 실제로 원격 프로세스에서 실행되는 함수인 반면, 로컬에서 호출하는 함수는 네트워크를 통해 적절한 메시지를 보내는 자동 생성된 Thunk이다.
<img src="https://velog.velcdn.com/images/doorbals_512/post/39ac6483-0029-408b-8f0b-873b74455146/image.png" alt=""></li>
</ul>
</li>
</ul>
<h3 id="4-withvalidation">4) WithValidation</h3>
<ul>
<li>Server RPC는 WithValidation으로 선언될 수도 있다. 이 경우 기존 함수와 동일한 인수를 사용하고, 해당 값을 신뢰할 수 있는지 여부를 나타내는 부울 값을 반환하는 <strong><code>&quot;_Validate&quot;</code></strong> 함수를 구현해야 한다. <ul>
<li>이는 치트와 같이 서버가 게임 플레이에 영향을 미치는 방식으로 클라이언트에서 전송된 데이터를 사용하는 경우를 감지하기 위한 수단이다. 서버 RPC가 유효성 검사에 실패하면 해당 RPC를 보낸 클라이언트가 게임에서 추방된다.
<img src="https://velog.velcdn.com/images/doorbals_512/post/83706457-bc15-4671-ae72-3c12d3383d4c/image.png" alt=""></li>
</ul>
</li>
<li>따라서 RPC는 즉시 전송되고 신뢰할 수 있으므로, Property Replication이 제한될 수 있는 특정 경우에 유용하다.</li>
</ul>
<hr>
<h2 id="property-replication-in-detail">Property Replication in Detail</h2>
<ul>
<li>RPC는 <strong><code>&quot;즉각적&quot;</code></strong>, Property Replication은 <strong><code>&quot;결국&quot;</code></strong>이 핵심 단어이다.
<img src="https://velog.velcdn.com/images/doorbals_512/post/bdf5ac90-fe18-4e44-83ed-ba6b6576af94/image.png" alt=""></li>
<li>서버에서 복제된 속성을 변경하면 모든 클라이언트가 결국 서버와 동기화 될 것을 기대할 수 있다. 플레이어가 너무 멀리 떨어져 액터와의 관련성이 사라질 때, 서버에서 액터가 변경되면 변경 사항은 계속 유지 된다. <strong>결국 액터가 다시 해당 클라이언트와 관련되면 업데이트된 속성 값을 받게 된다.</strong><ul>
<li>Property Replication은 어떤 경우에도 업데이트 빈도 및 대역폭 제한을 준수한다. 서버의 단일 프레임마다 복제된 속성을 변경할 수 있으며, 클라이언트는 업데이트 될 때마다 가장 최근 값을 가져온다. 즉, 서버는 모든 중간 지점의 값들을 하나씩 보내지 않아도 된다. (여기서는 업데이트 되는 값을 보간 해주었음.)
<a href="https://youtu.be/JOJP0CvpB8w?si=BX-73Ay2KhtJhPM8&amp;t=810">(동영상 링크) 13:30 ~ 14:05</a></li>
</ul>
</li>
<li>Property Replication을 활성화하려면 해당 속성의 UPROPERTY에 <strong>Replicated 지정자</strong>를 추가하면 된다.
  <img src="https://velog.velcdn.com/images/doorbals_512/post/b8ae61a8-3c56-499a-9eec-b0bf9e307daa/image.png" alt=""><ul>
<li>C++에서는 액터의 .cpp 파일에 <strong><code>GetLifetimeReplicatedProps()</code></strong> 함수를 정의해야 한다. (<strong>UnrealNetwork.h</strong>도 포함시키는 것이 좋다.) 이 함수에서는 복제해야 하는 속성과 조건을 지정한다. 가장 간단한 경우에는 속성이 항상 모든 클라이언트에 복제되며, 이 때는 DOREPLIFETIME 매크로에서 속성의 이름을 지정하여 이를 수행한다.
<img src="https://velog.velcdn.com/images/doorbals_512/post/8a2d59cd-ce88-496c-85a2-e966b26dbb97/image.png" alt=""></li>
<li>그러나 복제 조건을 지정할 수도 있는데, 아래와 같은 경우가 있다. (이외에도 존재)<ul>
<li>1) 소유 클라이언트에 속성을 복제하기만 하면 되는 경우
<img src="https://velog.velcdn.com/images/doorbals_512/post/62a0974e-499a-4f41-8376-f39ffe6b87dd/image.png" alt=""><ul>
<li>2) 업데이트를 받기 위해 비소유 클라이언트만 필요한 경우
<img src="https://velog.velcdn.com/images/doorbals_512/post/86dae31f-74ab-4f98-9ccd-fa45f0118292/image.png" alt=""></li>
<li>3) 생성 시 초기화되지만 런타임 시 변경되지 않는 경우 
<img src="https://velog.velcdn.com/images/doorbals_512/post/4b9f2381-0707-4c89-ab59-3158efab30cf/image.png" alt=""></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>복제된 속성이 업데이트 될 때 일부 코드를 실행해야 하는 경우 RepNotify 함수를 선언하고 ReplicatedUsing 지정자를 사용할 수 있다. 복제 업데이트로 인해 값이 변경될 때마다 지정된 알림 함수가 클라이언트에서 호출된다.
  <img src="https://velog.velcdn.com/images/doorbals_512/post/9232abf3-189e-4098-b7c9-b334a802eebb/image.png" alt=""><ul>
<li>블루프린트에서 서버의 속성 값을 변경하면 서버에서 연관된 RepNotify 함수가 자동으로 호출된다는 점에 유의해야 한다. (C++에서는 아님.)
<img src="https://velog.velcdn.com/images/doorbals_512/post/4acbf19a-55dc-47db-90a5-cac73dbd226c/image.png" alt=""></li>
<li>RepNotify 로직을 클라이언트뿐만 아니라 서버에서도 실행하려면 속성 값을 업데이트 한 후 RepNotify 함수를 수동으로 호출해야 한다.
<img src="https://velog.velcdn.com/images/doorbals_512/post/52658056-eeda-4077-b369-b3dda7f76b18/image.png" alt=""></li>
</ul>
</li>
</ul>
<hr>
<h2 id="authority--role">Authority &amp; Role</h2>
<ul>
<li>액터가 가질 수 있는 역할은 여러 가지가 존재하지만, <strong>액터에 대한 권한이 나에게 있는지</strong>만 고려하면 된다.<ul>
<li>Authority를 가지고 있는 것은 항상 서버이며, 클라이언트는 그저 업데이트를 받아 업데이트 사이에 액터에 시뮬레이션을 적용할 뿐이다.
<img src="https://velog.velcdn.com/images/doorbals_512/post/6a60b5f4-c779-480d-8ba7-c4871c16f1d7/image.png" alt=""></li>
</ul>
</li>
<li>액터 클래스에서 일부 코드를 실행할 때마다 권한을 확인할 수 있다.<ul>
<li><strong>권한 O</strong> : 싱글 플레이 모드에서 실행되거나, 코드가 서버에 의해 실행되고 있거나, 액터가 클라이언트에만 존재하는 경우</li>
<li><strong>권한 X</strong> : 클라이언트에서 실행되고, 이 액터는 서버에서 복제된 경우
<img src="https://velog.velcdn.com/images/doorbals_512/post/bff721c7-53bd-4f20-82d8-401bc42ae173/image.png" alt=""></li>
</ul>
</li>
<li>PlayerController가 소유 클라이언트에 복제되는 것은 AutonomousProxy이고, 연관된 Pawn 또한 해당 클라이언트에 대한 AutonomousProxy이다. 다른 모든 클라이언트의 경우 Pawn은 SimulatedProxy로 복제된다.<ul>
<li>AutonomousProxy는 클라이언트가 완전한 권한을 가지지는 못하더라도, 액터의 움직임과 행동을 직접적으로 제어한다는 것을 의미한다. (액터가 소유 클라이언트의 플레이어 컨트롤러에 의해 움직이는 경우)
<img src="https://velog.velcdn.com/images/doorbals_512/post/a729511d-b53c-4606-a998-85ebc7bdd011/image.png" alt=""></li>
</ul>
</li>
<li><strong>SimulatedProxy</strong>는 마지막으로 전송된 속도에 따라 시뮬레이션하고, <strong>AutonomousProxy</strong>는 플레이어 컨트롤러에 의해 입력된 정보에 따라 시뮬레이션한다.</li>
</ul>
<hr>
<p>👁️‍🗨️ 출처</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=JOJP0CvpB8w&amp;t=182s">https://www.youtube.com/watch?v=JOJP0CvpB8w&amp;t=182s</a></li>
<li><a href="https://docs.unrealengine.com/4.27/ko/InteractiveExperiences/Networking/Actors/">https://docs.unrealengine.com/4.27/ko/InteractiveExperiences/Networking/Actors/</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Unreal]언리얼 엔진 멀티 플레이어 시스템 Part1]]></title>
            <link>https://velog.io/@doorbals_512/%EC%96%B8%EB%A6%AC%EC%96%BC-%EC%97%94%EC%A7%84-%EB%A9%80%ED%8B%B0-%ED%94%8C%EB%A0%88%EC%9D%B4%EC%96%B4-%EC%8B%9C%EC%8A%A4%ED%85%9C-Part1</link>
            <guid>https://velog.io/@doorbals_512/%EC%96%B8%EB%A6%AC%EC%96%BC-%EC%97%94%EC%A7%84-%EB%A9%80%ED%8B%B0-%ED%94%8C%EB%A0%88%EC%9D%B4%EC%96%B4-%EC%8B%9C%EC%8A%A4%ED%85%9C-Part1</guid>
            <pubDate>Tue, 15 Aug 2023 09:27:47 GMT</pubDate>
            <description><![CDATA[<h2 id="언리얼-엔진-멀티-플레이어-시스템">언리얼 엔진 멀티 플레이어 시스템</h2>
<ul>
<li>언리얼 엔진에는 멀티 플레이어 게임을 위한 네트워킹 시스템이 제공된다.</li>
<li>멀티 플레이어 게임은 동일한 게임의 여러 인스턴스가 동시에 실행되는 것을 목표로 한다.</li>
<li>즉, 아래의 예시들이 여러 인스턴스에 해당된다.<ul>
<li>1) 동일한 시스템의 다른 프로세스 </li>
<li>2) 동일한 로컬 네트워크의 다른 컴퓨터 </li>
<li>3) 인터넷을 통해 서로 다른 위치에 존재하는 플레이어</li>
</ul>
</li>
<li>이처럼 다양한 게임 인스턴스는 서로 소통함으로써 공유하는 세계를 일관되게 구축해야 한다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/doorbals_512/post/24f23a38-d474-412b-bae2-8fdfdb84a9fe/image.png" alt=""></p>
<ul>
<li>언리얼 엔진의 네트워크 모델에서 플레이어(Client)는 서버에 연결 상태를 유지하고, 서버는 월드를 신뢰할 수 있는 상태(Reliable)로 유지한다.</li>
<li>서버에서 변경 사항이 발생하면 해당 변경 사항은 <strong>리플리케이션(Replication)</strong>이라는 프로세스의 일부로 필요에 따라 클라이언트로 전파된다.<ul>
<li>리플리케이션 시스템은 게임 코드와 통합되어 멀티 플레이어 게임을 쉽게 개발하도록 해준다.</li>
<li>통신을 위해 소켓을 따로 열거나 패킷을 보내지 않아도 되며, 데이터 직렬화(Serealization) / 인코딩(Encoding) / 바이트 순서 / 타임스탬프 / 재정렬 및 라우팅(Routing) 등을 처리하지 않고, 단순히 해당 프로퍼티를 복제하기를 요구하면 복제되는 시스템이다.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="net-mode">Net Mode</h2>
<ul>
<li>World의 속성으로 네 가지의 모드(Standalone / DedicatedServer / ListenServer / Client)가 존재하며, 아래의 3가지 질문을 통해 모드를 나눌 수 있다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/doorbals_512/post/f1d53a98-329e-4d1d-a4b3-be41d0d7b369/image.png" alt=""></p>
<h3 id="1-playable">1. Playable?</h3>
<ul>
<li>게임을 플레이 할 수 있는가?</li>
<li>GameInstance에 LocalPlayer가 존재하고, 해당 플레이어의 입력을 처리하고, 월드를 뷰포트로 렌더링하고 있는가?<h3 id="2-authority">2. Authority?</h3>
</li>
<li>우리는 서버인가?</li>
<li>GameInstance에 GameMode 액터가 포함된 World의 정식 사본이 있는가?<h3 id="3-open-to-clients">3. Open To Clients?</h3>
</li>
<li>(우리가 서버라면) 원격 연결 시도를 위해 열려있는가?</li>
<li>다른 플레이어가 클라이언트로 참여해 게임을 플레이 할 수 있는가?</li>
</ul>
<p><img src="https://velog.velcdn.com/images/doorbals_512/post/54b84923-5a13-44df-ae44-1486b5e0da8e/image.png" alt=""></p>
<ul>
<li>게임을 시작하면 프로세스의 수명동안 유지되는 GameInstance 개체를 얻은 다음, 게임에서 URL(서버 주소 또는 맵 이름)을 탐색한다.</li>
<li>이후 게임이 맵을 로드하게 되고, 맵은 World를 제공하며, 이 World의 Net Mode는 GameInstance가 시작된 방식에 따라 달라진다.</li>
</ul>
<hr>
<h2 id="4가지-net-mode의-차이점">4가지 Net Mode의 차이점</h2>
<h3 id="1-standalone">1. Standalone</h3>
<ul>
<li>게임 인스턴스가 로컬로 맵을 로드한 경우, World의 Net Mode는 Standalone이 된다.</li>
<li>단일 게임 인스턴스는 서버이자 클라이언트이지만, 단일 플레이어 구성에서 실행되기 때문에 다른 클라이언트가 연결할 수 없다.
<img src="https://velog.velcdn.com/images/doorbals_512/post/5efe676c-7ed8-4a00-a180-8747f2e6a600/image.png" alt=""></li>
</ul>
<h3 id="2-dedicated-server">2. Dedicated Server</h3>
<ul>
<li>로컬 플레이어와 뷰포트가 없는 게임 인스턴스</li>
<li>사운드, 그래픽, 사용자 입력 등 플레이어 관련 기능을 제거하여 효율적인 실행 가능한 서버</li>
<li>플레이어가 클라이언트로 연결할 수 있는 서버 전용 콘솔 응용 프로그램으로서 작동한다.
<img src="https://velog.velcdn.com/images/doorbals_512/post/2b9948a1-0c55-4d48-9205-378b15dd6896/image.png" alt=""></li>
</ul>
<h3 id="3-listen-server">3. Listen Server</h3>
<ul>
<li>맵을 로컬로 로드하지만 Listen 옵션을 추가하면 Listen Server로 실행된다.</li>
<li>기본적으로 Standalone과 동일하지만 게임의 다른 인스턴스들이 클라이언트로 연결할 수 있다.
<img src="https://velog.velcdn.com/images/doorbals_512/post/8d985441-b881-4a3a-a41e-70ad1ed950b7/image.png" alt=""></li>
</ul>
<h3 id="4-client">4. Client</h3>
<ul>
<li>게임 인스턴스가 원격 서버로 연결된 경우, 해당 World는 Client 모드를 갖는다.</li>
<li>이 때 게임은 로컬 플레이어가 플레이 할 수 있지만, 서버 요청에 따라 World가 업데이트 된다.</li>
<li>서버가 아닌 유일한 모드로, 서버 측의 로직이 실행되지 않는다.
<img src="https://velog.velcdn.com/images/doorbals_512/post/094fac5b-03e4-4219-b95c-6736a3cb0c86/image.png" alt=""></li>
</ul>
<hr>
<h2 id="언리얼-엔진의-네트워킹-시나리오">언리얼 엔진의 네트워킹 시나리오</h2>
<p><img src="https://velog.velcdn.com/images/doorbals_512/post/b77c23ed-7e09-4309-9b12-62cdff949e8f/image.png" alt=""></p>
<h3 id="1-single-player">1. Single Player</h3>
<ul>
<li>하나의 게임 인스턴스가 있고, 해당 World는 Standalone 모드에서 실행된다.</li>
</ul>
<h3 id="2-multi-player">2. Multi Player</h3>
<ul>
<li>각각 고유한 게임 인스턴스들이 있고, 고유한 World 복사본이 있는 여러 프로세스가 있다.</li>
<li>이러한 프로세스 중 하나는 Listen Server 또는 Dedicated Server가 된다.</li>
</ul>
<hr>
<h2 id="replication-시스템-개요">Replication 시스템 개요</h2>
<p><img src="https://velog.velcdn.com/images/doorbals_512/post/c8b8cf63-9e7e-46fe-bb9b-1d5086958f5e/image.png" alt=""></p>
<ul>
<li>멀티 플레이어 게임이 실행되면 언리얼 엔진의 Replication 시스템이 동작하여 게임의 다양한 인스턴스가 모두 동기화되도록 한다.</li>
<li>각 인스턴스는 공유되는 World에 대해 자체적으로 복사본을 만들고, Replication 시스템은 이것이 정확하게 동작하는지 확인한다.</li>
<li>이러한 역할 수행을 위해 Replication 시스템은 NetDriver, NetConnection, Channel 라는 클래스들에 의존한다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/doorbals_512/post/0cbb7225-49b6-4bc4-90a8-92ce761bd12a/image.png" alt=""></p>
<ul>
<li>서버를 부팅하면 NetDriver가 생성되고, 원격 프로세스로부터의 메시지 수신이 시작된다.</li>
<li>클라이언트를 부팅하면 자체 NetDriver가 생성되어 서버에 연결 요청을 보낸다.</li>
<li>서버와 클라이언트의 NetDriver가 연결되면 각 NetDriver 내에서 NetConnection이 설정된다. <ul>
<li>서버에는 연결된 각 원격 플레이어에 대해 하나의 NetConnection이 존재한다.</li>
<li>각 클라이언트에는 서버에 대한 연결을 나타내는 단일 NetConnection이 존재한다.</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/doorbals_512/post/effc75fa-0c14-4f19-89df-29c988ae774b/image.png" alt=""></p>
<ul>
<li>모든 NetConnection에는 다양한 채널이 존재한다.</li>
<li>일반적으로 NetConnection에는 ControlChannel, VoiceChannel이 있고, 각 NetConnection에는 현재 해당 NetConnection을 통해 복제되고 있는 각 액터들에 대한 ActorChannel들이 있다.<ul>
<li>이를 통해 Replicate는 Actor 수준에서 발생한다는 것을 알 수 있다.</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/doorbals_512/post/adb5a324-3b0c-4670-96fc-aa00fcb89b1a/image.png" alt=""></p>
<ul>
<li>네트워크를 통해 동기화를 유지하기 위해 액터가 필요한 경우, 해당 액터를 Replication 가능하도록 설정한다.</li>
<li>Replicate 가능한 액터가 특정 플레이어와 관련된 것이면 서버는 해당 플레이어의 NetConnection에서 ActorChannel을 만들고, 서버와 클라이언트는 해당 채널을 사용하여 해당 액터에 대한 정보를 교환한다.</li>
</ul>
<hr>
<h2 id="actor-replication">Actor Replication</h2>
<ul>
<li>액터가 Replicate 될 때, 세 가지의 결과가 발생한다.</li>
</ul>
<h3 id="1-lifetime">1) LifeTime</h3>
<ul>
<li>액터의 수명이 서버와 클라이언트 간에 동기화되어 유지된다.</li>
<li>서버가 Replicate 설정된 액터를 생성하면 클라이언트에 알림이 전달되어 자체적인 복사본을 생성한다.</li>
<li>액터가 서버에서 소멸되면 클라이언트에서도 자동으로 소멸된다.
  <img src="https://velog.velcdn.com/images/doorbals_512/post/8bab8ce4-5cec-4c70-8da2-bc35ebd79106/image.png" alt=""></li>
</ul>
<h3 id="2-property-replication">2) Property Replication</h3>
<ul>
<li>액터에 Replicate 플래그가 지정된 속성이 있는 경우, 해당 속성이 서버에서 변경되면 새 값이 클라이언트에 전달된다.
  <img src="https://velog.velcdn.com/images/doorbals_512/post/3b673d62-09d0-433e-9d11-1cb5867b3722/image.png" alt=""></li>
</ul>
<h3 id="3-rpcsremote-procedure-call">3) RPCs(Remote Procedure Call)</h3>
<ul>
<li>RPC에는 3가지 종류가 있다.<ul>
<li>Client : 서버에서 호출되지만 클라이언트에서 실행되는 RPC<ul>
<li>해당 액터를 실제 소유하고 있는 클라이언트에서만 함수 실행</li>
</ul>
</li>
<li>Server : 클라이언트에서 호출되지만 서버에서 실행되는 RPC<ul>
<li>클라이언트는 RPC가 호출되는 액터를 소유해야 한다.</li>
</ul>
</li>
<li>Multicast : 서버에서 호출된 후 서버를 포함하여 연결된 모든 클라이언트에서 실행되는 RPC<ul>
<li>클라이언트에서 호출될 경우, 로컬에서만 실행되고 서버에서는 실행 X</li>
</ul>
</li>
</ul>
</li>
<li>RPC를 사용하여 서버와 클라이언트 간에 메시지를 주고 받을 수 있다.</li>
<li>실행될 때만 Replicate 된다.
  <img src="https://velog.velcdn.com/images/doorbals_512/post/bb30bcc4-8041-48e2-9e67-1f1843a56f23/image.png" alt=""></li>
</ul>
<hr>
<h2 id="ownership-소유권">Ownership (소유권)</h2>
<ul>
<li><p>모든 액터는 소유자로 지정된 다른 액터를 가질 수 있다.
  <img src="https://velog.velcdn.com/images/doorbals_512/post/4ece0b30-dc0c-443a-a1f1-68d16ac823d7/image.png" alt=""></p>
</li>
<li><p>일반적으로 액터 생성 시 소유자를 설정하지만, 런타임에 SetOwner를 호출할 수도 있다.
  <img src="https://velog.velcdn.com/images/doorbals_512/post/c33ee412-7dd5-42b7-880c-c76a0be8ec30/image.png" alt=""></p>
<p>  <img src="https://velog.velcdn.com/images/doorbals_512/post/a8a28b68-ace1-4a40-a29d-e74934a2b530/image.png" alt=""></p>
</li>
<li><p>NetConnection은 플레이어를 나타내며, 플레이어가 게임에 로그인되면 이와 연결된 PlayerController 액터를 소유한다. 이 경우 NetConnection은 다시 PlayerController로 추적할 수 있는 모든 액터를 소유한다.
  <img src="https://velog.velcdn.com/images/doorbals_512/post/5e8c6004-2272-4992-913d-734a2f03503f/image.png" alt=""></p>
</li>
<li><p>아래와 같은 경우, Weapon 액터로부터 시작해 PlayerController에 대한 소유자 참조 체인을 따라가면서 해당 Weapon 액터가 특정 클라이언트 연결에 속해 있는지 알 수 있다.
  <img src="https://velog.velcdn.com/images/doorbals_512/post/022fbed7-8789-48c5-9cbc-a00b126a727e/image.png" alt=""></p>
</li>
</ul>
<hr>
<h2 id="enabling-actor-replication">Enabling Actor Replication</h2>
<ul>
<li>액터가 Replicate 되려면 bReplicates 플래그가 활성화 되어야 한다.
  <img src="https://velog.velcdn.com/images/doorbals_512/post/355a202f-f685-43d2-86bd-86f2e9a9928a/image.png" alt=""></li>
<li>런타임에 Replicate를 켜거나 끌 수도 있다.
  <img src="https://velog.velcdn.com/images/doorbals_512/post/deb53fe2-d517-4344-ac94-8025ca0bbdfd/image.png" alt=""></li>
<li>서버는 해당 액터를 해당 클라이언트에 Replicate 하기 위해 지정된 NetConnection 내에서 ActorChannel을 열 수 있다.
  <img src="https://velog.velcdn.com/images/doorbals_512/post/a9c8cf78-d1ba-4c58-8ef0-9f3d35bd12ee/image.png" alt=""></li>
</ul>
<hr>
<h2 id="relevancy-관련성">Relevancy (관련성)</h2>
<ul>
<li>액터의 관련성에 따라 어떤 연결이 언제 발생할지 결정된다.</li>
<li>액터가 Replicate 가능할 때, 서버의 NetDriver는 각 클라이언트 연결에 대해 해당 액터를 확인해 해당 액터가 해당 클라이언트와 관련 있는지 확인한다.
  <img src="https://velog.velcdn.com/images/doorbals_512/post/646dc280-4e5d-4cec-85fe-b029c5870f84/image.png" alt=""></li>
<li>일부 액터는 Replicate가 가능한 한, 서버가 이를 항상 모든 클라이언트에 복제한다. (bAlwaysRelevant = true)
  <img src="https://velog.velcdn.com/images/doorbals_512/post/838e12c8-c960-48c6-8bc1-da3900ce0395/image.png" alt=""><ul>
<li>예를 들면, GameState 및 PlayerState는 항상 모든 클라이언트와 관련이 있다.
  <img src="https://velog.velcdn.com/images/doorbals_512/post/84278d20-469e-46c2-9e60-2e4cb91e0b7f/image.png" alt=""></li>
</ul>
</li>
<li>소유권은 관련성에 있어 중요한 역할을 한다. 특정 플레이어가 소유하거나 특정 행동을 지시하는 액터는 항상 해당 클라이언트와 관련된 것으로 간주한다. 일부 액터(ex. PlayerController)는 소유자에게만 관련되도록 구성되어 있으므로 소유하지 않은 클라이언트에는 복제되지 않는다.
  <img src="https://velog.velcdn.com/images/doorbals_512/post/dd346c15-24eb-471e-9231-a72314ef9d2d/image.png" alt=""></li>
<li>소유자로부터 관련성을 상속하도록 액터를 구성할 수도 있다.
  <img src="https://velog.velcdn.com/images/doorbals_512/post/8873b0d9-70c8-4e14-9df1-562059f91821/image.png" alt=""></li>
<li>위와 같은 특수 플래그 중 어느 것도 설정되지 않고, 어떤 클라이언트도 액터를 소유하지 않은 경우도 있다.
  <img src="https://velog.velcdn.com/images/doorbals_512/post/fbe58985-51b6-4ee9-9e19-566c12f77204/image.png" alt=""><ul>
<li>해당 경우에는 액터가 숨겨져 있고, 충돌이 비활성화 되어있으면 관련성이 없는 것으로 간주된다.
  <img src="https://velog.velcdn.com/images/doorbals_512/post/bc4c846f-173a-4b2b-ad77-e26467efd33a/image.png" alt=""></li>
<li>그렇지 않은 경우 관련성은 클라이언트 연결에 해당하는 플레이어와의 거리를 기반으로 한다. 액터에서 플레이어까지의 제곱 거리가 NetCullDistanceSquared보다 작으면 액터는 해당 플레이어와 관련이 있다.
<img src="https://velog.velcdn.com/images/doorbals_512/post/743bb300-d8ad-43cc-a240-d5f457ae3468/image.png" alt=""></li>
</ul>
</li>
</ul>
<hr>
<h2 id="update-frequency--priority-업데이트-빈도-및-우선순위">Update Frequency &amp; Priority (업데이트 빈도 및 우선순위)</h2>
<ul>
<li>액터가 Replicate 되면 업데이트 빈도 및 우선순위에 따라 서버가 해당 액터와 관련된 클라이언트에 업데이트를 보내는 빈도가 결정된다.
  <img src="https://velog.velcdn.com/images/doorbals_512/post/bcf07881-ed7b-443e-b693-4b7267ae2bc7/image.png" alt=""></li>
<li>NetUpdateFrequency를 설정할 경우, 액터를 확인하여 변경사항이 있을 경우 잠재적으로 클라이언트에 새 데이터를 보내는 초당 횟수가 결정된다.
  <img src="https://velog.velcdn.com/images/doorbals_512/post/351ff44e-f873-410e-ba0a-d6e6e2f35f1d/image.png" alt=""></li>
<li>서버의 NetDriver는 대역폭 포화를 완화하기 위해 몇 가지 간단한 로드 밸런싱을 사용한다. NetDriver가 작업할 수 있는 대역폭은 한정되어 있으므로, 우선순위에 따라 관련 액터를 정렬한 뒤, 사용 가능한 대역폭을 모두 사용할 때까지 네트워크 업데이트를 실행한다.<ul>
<li>플레이어에 더 가까운 액터는 더 높은 우선순위로 가중치가 부여되고, 한동안 업데이트 되지 않은 액터에도 더 높은 우선순위가 부여되므로 모든 액터는 결국 언젠가는 우선순위에 따라 작업된다.
<img src="https://velog.velcdn.com/images/doorbals_512/post/bada0acd-d936-4771-99fa-54a5437e77e1/image.png" alt=""></li>
</ul>
</li>
<li>액터의 NetPriority 속성을 설정하면 추가적인 가중치를 부여할 수 있다.<ul>
<li>예를 들면, NetPriority를 5.0으로 설정할 경우 그렇지 않은 경우보다 5배 더 자주 업데이트 되어야 한다고 지시하는 것이다.<br><img src="https://velog.velcdn.com/images/doorbals_512/post/3a88abd3-4a39-48fd-af5a-9fdb34fb4e31/image.png" alt=""></li>
</ul>
</li>
</ul>
<hr>
<h3 id="👁️🗨️-출처">👁️‍🗨️ 출처</h3>
<ul>
<li><a href="https://www.youtube.com/watch?v=JOJP0CvpB8w&amp;t=182s">https://www.youtube.com/watch?v=JOJP0CvpB8w&amp;t=182s</a></li>
<li><a href="https://docs.unrealengine.com/5.2/ko/networking-overview-for-unreal-engine/">https://docs.unrealengine.com/5.2/ko/networking-overview-for-unreal-engine/</a></li>
<li><a href="https://docs.unrealengine.com/4.27/ko/InteractiveExperiences/Networking/Actors/">https://docs.unrealengine.com/4.27/ko/InteractiveExperiences/Networking/Actors/</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 18436번 : 수열과 쿼리 37]]></title>
            <link>https://velog.io/@doorbals_512/%EB%B0%B1%EC%A4%80-18436%EB%B2%88-%EC%88%98%EC%97%B4%EA%B3%BC-%EC%BF%BC%EB%A6%AC-37</link>
            <guid>https://velog.io/@doorbals_512/%EB%B0%B1%EC%A4%80-18436%EB%B2%88-%EC%88%98%EC%97%B4%EA%B3%BC-%EC%BF%BC%EB%A6%AC-37</guid>
            <pubDate>Fri, 17 Mar 2023 04:37:34 GMT</pubDate>
            <description><![CDATA[<h2 id="🔗-문제-링크">🔗 문제 링크</h2>
<p><a href="https://www.acmicpc.net/problem/18436">https://www.acmicpc.net/problem/18436</a></p>
<hr>
<h2 id="✍️-문제-풀이">✍️ 문제 풀이</h2>
<p>해당 문제는 수열의 <strong>특정 구간 내의 짝수 개수와 홀수 개수</strong>를 구하는 문제로, <strong>세그먼트 트리</strong> 알고리즘을 사용하여 풀이했다.</p>
<p><strong>1)</strong> 수열을 저장할 <code>datas</code>, pair(짝수 개수, 홀수 개수) 트리를 저장할 <code>tree</code> 배열을 선언한다. 이 때 트리의 크기는 수열 배열의 최대 크기의 4배 이상으로 설정한다.</p>
<p><strong>2)</strong> 수의 개수 <code>n</code>을 입력받아 저장하고, n개의 수를 입력받아 <code>datas</code>에 저장한다. 이후 쿼리의 개수 <code>m</code>을 입력 받아 저장한다. </p>
<p><strong>3)</strong> <code>init()</code> 함수를 통해 pair(짝수 개수, 홀수 개수) 트리를 초기화 한다.</p>
<ul>
<li>최상위 노드부터 시작해 데이터 범위를 반씩 분할하여 구간곱을 저장하는 방식으로 트리를 채운다. 재귀를 통해 가장 작은 범위의 값을 구해, 그 값들로 더 큰 범위의 값을 구한다.<ul>
<li>현재 순서 노드의 값 = pair(왼쪽 자식 노드의 짝수 개수 + 오른쪽 자식 노드의 짝수 개수, 왼쪽 자식 노드의 홀수 개수 + 오른쪽 자식 노드의 홀수 개수)</li>
</ul>
</li>
<li>더 이상 분할할 수 없을 때(범위 내에 수가 하나 뿐일 때)<ul>
<li>해당 수가 짝수라면 pair(1, 0)를 현재 노드의 값으로 초기화<ul>
<li>해당 수가 홀수라면 pari(0, 1)를 현재 노드의 값으로 초기화</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><strong>4)</strong> <code>m</code>번의 쿼리를 실행한다.</p>
<ul>
<li>쿼리로 주어지는 수 a, b, c를 입력 받는다.</li>
<li>a가 1이라면 <code>update()</code>를 실행하고, b번째 수를 c로 변경한다.<ul>
<li><code>update()</code>는 하위 노드 값부터 상위 노드로 바뀌는 방식으로 진행한다.</li>
<li>범위 밖에 있는 경우 무시한다.</li>
<li>범위 안에 있다면 <ul>
<li>더 이상 분할할 수 없을 때(범위 내에 수가 하나 뿐일 때)<ul>
<li>변경할 수가 짝수라면 현재 순서 노드를 pair(1, 0)으로 변경한다.</li>
<li>변경할 수가 홀수라면 현재 순서 노드를 pair(0, 1)으로 변경한다.</li>
</ul>
</li>
<li>분할할 수 있다면 데이터 범위를 반으로 분할해 
현재 순서 노드의 값 = pair(왼쪽 자식 노드의 짝수 개수 + 오른쪽 자식 노드의 짝수 개수, 왼쪽 자식 노드의 홀수 개수 + 오른쪽 자식 노드의 홀수 개수)로 변경한다.</li>
<li><code>update()</code> 함수 실행 이후 <code>datas</code>의 특정 인덱스 값도 변경해주어야 한다.</li>
</ul>
</li>
</ul>
</li>
<li>a가 2라면 b, c 범위 내 짝수 개수를 출력한다.</li>
<li>a가 3이라면 b, c 범위 내 홀수 개수를 출력한다.<ul>
<li><code>get()</code> 함수를 통해 현재 순서에서 구해야하는 범위의 짝수 개수 및 홀수 개수를 구한다.<ul>
<li>범위 밖에 있는 경우 pair(0, 0)을 반환한다.</li>
<li>범위 안에 있는 경우 현재 순서 노드의 값을 반환한다.</li>
<li>범위에 걸쳐 있는 경우 현재 범위의 절반으로 나누어 각 범위에 대해 <code>get()</code>을 실행한다.</li>
</ul>
</li>
<li>짝수 개수는 get().first를, 홀수 개수는 second를 출력하면 된다.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="🖥️-풀이-코드">🖥️ 풀이 코드</h2>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;algorithm&gt;
using namespace std;
typedef pair&lt;long long, int&gt; pii;    // 짝수, 홀수 개수

pii tree[400004];
int datas[100001];
int n, m;

pii init(int start, int end, int node)
{
    if (start == end)
    {
        if (datas[start] % 2 == 0)
            return tree[node] = pii(1, 0);
        else
            return tree[node] = pii(0, 1);
    }
    int mid = start + (end - start) / 2;
    pii leftNode = init(start, mid, node * 2);
    pii rightNode = init(mid + 1, end, node * 2 + 1);
    return tree[node] = pii(leftNode.first + rightNode.first,
        leftNode.second + rightNode.second);
}

pii get(int start, int end, int node, int left, int right)
{
    if (end &lt; left || right &lt; start) return pii(0, 0);
    if (left &lt;= start &amp;&amp; end &lt;= right)
        return tree[node];
    int mid = start + (end - start) / 2;
    pii leftNode = get(start, mid, node * 2, left, right);
    pii rightNode = get(mid + 1, end, node * 2 + 1, left, right);
    return pii(leftNode.first + rightNode.first, leftNode.second + rightNode.second);
}

pii update(int start, int end, int node, int index, int dif)
{
    if (index &lt; start || end &lt; index) return tree[node];
    if (start == end)
    {
        if (dif % 2 == 0)
            return tree[node] = pii(1, 0);
        else
            return tree[node] = pii(0, 1);
    }
    int mid = start + (end - start) / 2;
    pii leftNode = update(start, mid, node * 2, index, dif);
    pii rightNode = update(mid + 1, end, node * 2 + 1, index, dif);
    return tree[node] = pii(leftNode.first + rightNode.first, leftNode.second + rightNode.second);
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr); cout.tie(nullptr);

    cin &gt;&gt; n;
    for (int i = 1; i &lt;= n; i++)
        cin &gt;&gt; datas[i];
    init(1, n, 1);
    cin &gt;&gt; m;
    for (int i = 0; i &lt; m; i++)
    {
        int a, b, c;
        cin &gt;&gt; a &gt;&gt; b &gt;&gt; c;
        if (a == 1)
        {
            update(1, n, 1, b, c);
            datas[b] = c;
        }
        else if (a == 2)
            cout &lt;&lt; get(1, n, 1, b, c).first &lt;&lt; &#39;\n&#39;;
        else
            cout &lt;&lt; get(1, n, 1, b, c).second &lt;&lt; &#39;\n&#39;;
    }
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 2357번 : 최솟값과 최댓값]]></title>
            <link>https://velog.io/@doorbals_512/%EB%B0%B1%EC%A4%80-2357%EB%B2%88-%EC%B5%9C%EC%86%9F%EA%B0%92%EA%B3%BC-%EC%B5%9C%EB%8C%93%EA%B0%92-333lmdki</link>
            <guid>https://velog.io/@doorbals_512/%EB%B0%B1%EC%A4%80-2357%EB%B2%88-%EC%B5%9C%EC%86%9F%EA%B0%92%EA%B3%BC-%EC%B5%9C%EB%8C%93%EA%B0%92-333lmdki</guid>
            <pubDate>Fri, 17 Mar 2023 03:56:17 GMT</pubDate>
            <description><![CDATA[<h2 id="🔗-문제-링크">🔗 문제 링크</h2>
<p><a href="https://www.acmicpc.net/problem/2357">https://www.acmicpc.net/problem/2357</a></p>
<hr>
<h2 id="✍️-문제-풀이">✍️ 문제 풀이</h2>
<p>해당 문제는 수열의 <strong>특정 구간 내의 최솟값과 최댓값</strong>을 구하는 문제로, <strong>세그먼트 트리</strong> 알고리즘을 사용하여 풀이했다.</p>
<p><strong>1)</strong> 수열을 저장할 <code>datas</code>, 최솟값 트리를 저장할 <code>minTree</code> 배열, 최댓값 트리를 저장할 <code>maxTree</code> 배열을 선언한다. 이 때 트리들의 크기는 수열 배열의 최대 크기의 4배 이상으로 설정한다.</p>
<p><strong>2)</strong> 수의 개수 <code>n</code>과 최솟값 및 최댓값을 구해야하는 범위의 개수 <code>m</code>을 입력 받아 저장하고, n개의 수를 입력받아 <code>datas</code>에 저장한다.</p>
<p><strong>3)</strong> <code>minInit()</code>, <code>maxInit()</code> 함수를 통해 최솟값 트리와 최댓값 트리를 초기화 한다.</p>
<ul>
<li>최상위 노드부터 시작해 데이터 범위를 반씩 분할하여 구간곱을 저장하는 방식으로 트리를 채운다. 재귀를 통해 가장 작은 범위의 값을 구해, 그 값들로 더 큰 범위의 값을 구한다.</li>
<li><strong>최솟값</strong> 트리의 경우<ul>
<li>현재 순서 노드의 값 = 왼쪽 자식 노드의 값과 오른쪽 자식 노드의 값 중 더 작은 값</li>
</ul>
</li>
<li><strong>최댓값</strong> 트리의 경우<ul>
<li>현재 순서 노드의 값 = 왼쪽 자식 노드의 값과 오른쪽 자식 노드의 값 중 더 큰 값</li>
</ul>
</li>
<li>더 이상 분할할 수 없을 때(범위 내에 수가 하나 뿐일 때) 해당 수를 현재 순서 노드의 값으로 설정하고 반환한다.</li>
</ul>
<p><strong>4)</strong> <code>m</code>번의 최솟값 및 최댓값 출력을 실행한다.</p>
<ul>
<li>항상 a &lt; b임이 보장되지 않기 때문에  a &lt; b일 때와 아닐 때의 조건을 나눈다.</li>
<li><code>findMin()</code>과 <code>findMax()</code> 함수를 통해 현재 순서에서 구해야하는 범위의 최솟값 및 최댓값을 구한다.</li>
<li><strong>최솟값</strong> 트리의 경우<ul>
<li>범위 밖에 있는 경우 INF(수열의 수로 올 수 없는 큰 값)을 반환한다.</li>
<li>범위 안에 있는 경우 현재 순서 노드의 최솟값을 반환한다.</li>
<li>범위에 걸쳐 있는 경우 현재 범위의 절반으로 나누어 각 범위에 대해 <code>findMin()</code>을 실행한다.</li>
</ul>
</li>
<li><strong>최댓값</strong> 트리의 경우<ul>
<li>범위 밖에 있는 경우 0을 반환한다.</li>
<li>범위 안에 있는 경우 현재 순서 노드의 최댓값을 반환한다.</li>
<li>범위에 걸쳐 있는 경우 현재 범위의 절반으로 나누어 각 범위에 대해 <code>findMax()</code>을 실행한다.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="🖥️-풀이-코드">🖥️ 풀이 코드</h2>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;algorithm&gt;
using namespace std;

int n, m;
long long minTree[400004];
long long maxTree[400004];
long long datas[100001];
const long long INF = 1000000001;

long long minInit(int start, int end, int node)
{
    if (start == end) return minTree[node] = datas[start];
    int mid = start + (end - start) / 2;
    return minTree[node] = min(minInit(start, mid, node * 2), minInit(mid + 1, end, node * 2 + 1));
}

long long findMin(int start, int end, int node, int left, int right)
{
    if (end &lt; left || right &lt; start) return INF;
    if (left &lt;= start &amp;&amp; end &lt;= right) return minTree[node];
    int mid = start + (end - start) / 2;
    return min(findMin(start, mid, node * 2, left, right), 
        findMin(mid + 1, end, node * 2 + 1, left, right));
}

long long maxInit(int start, int end, int node)
{
    if (start == end) return maxTree[node] = datas[start];
    int mid = start + (end - start) / 2;
    return maxTree[node] = max(maxInit(start, mid, node * 2), maxInit(mid + 1, end, node * 2 + 1));
}

long long findMax(int start, int end, int node, int left, int right)
{
    if (end &lt; left || right &lt; start) return 0;
    if (left &lt;= start &amp;&amp; end &lt;= right) return maxTree[node];
    int mid = start + (end - start) / 2;
    return max(findMax(start, mid, node * 2, left, right),
        findMax(mid + 1, end, node * 2 + 1, left, right));
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr); cout.tie(nullptr);

    cin &gt;&gt; n &gt;&gt; m;
    for (int i = 1; i &lt;= n; i++)
        cin &gt;&gt; datas[i];

    minInit(1, n, 1);
    maxInit(1, n, 1);
    for (int i = 0; i &lt; m; i++)
    {
        int a, b;
        cin &gt;&gt; a &gt;&gt; b;
        if (a &lt; b)
        {
            cout &lt;&lt; findMin(1, n, 1, a, b) &lt;&lt; &#39; &#39;;
            cout &lt;&lt; findMax(1, n, 1, a, b) &lt;&lt; &#39;\n&#39;;
        }
        else
        {
            cout &lt;&lt; findMin(1, n, 1, b, a) &lt;&lt; &#39; &#39;;
            cout &lt;&lt; findMax(1, n, 1, b, a) &lt;&lt; &#39;\n&#39;;
        }    
    }
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 11505번 : 구간 곱 구하기]]></title>
            <link>https://velog.io/@doorbals_512/%EB%B0%B1%EC%A4%80-11505%EB%B2%88-%EA%B5%AC%EA%B0%84-%EA%B3%B1-%EA%B5%AC%ED%95%98%EA%B8%B0-vr72metx</link>
            <guid>https://velog.io/@doorbals_512/%EB%B0%B1%EC%A4%80-11505%EB%B2%88-%EA%B5%AC%EA%B0%84-%EA%B3%B1-%EA%B5%AC%ED%95%98%EA%B8%B0-vr72metx</guid>
            <pubDate>Fri, 17 Mar 2023 03:41:50 GMT</pubDate>
            <description><![CDATA[<h2 id="🔗-문제-링크">🔗 문제 링크</h2>
<p><a href="https://www.acmicpc.net/problem/11505">https://www.acmicpc.net/problem/11505</a></p>
<hr>
<h2 id="✍️-문제-풀이">✍️ 문제 풀이</h2>
<p>해당 문제는 수열의 <strong>특정 구간 곱</strong>을 구하는 문제로, <strong>세그먼트 트리</strong> 알고리즘을 사용하여 풀이했다.</p>
<p><strong>1)</strong> 수열을 저장할 <code>datas</code>, 구간합 트리를 저장할 <code>tree</code> 배열을 선언한다. 이 때 구간합 트리의 크기는 수열 배열의 최대 크기의 4배 이상으로 설정한다.</p>
<p><strong>2)</strong> 수의 개수 <code>n</code>과 수의 변경이 일어나는 횟수 <code>m</code>, 구간 곱을 구하는 횟수 <code>k</code> 를 입력 받아 저장하고, n개의 수를 입력받아 <code>datas</code>에 저장한다.</p>
<p><strong>3)</strong> <code>init()</code> 함수를 통해 구간합 트리를 초기화 한다.</p>
<ul>
<li>최상위 노드부터 시작해 데이터 범위를 반씩 분할하여 구간곱을 저장하는 방식으로 트리를 채운다. 재귀를 통해 가장 작은 범위의 값을 구해, 그 값들로 더 큰 범위의 값을 구한다.</li>
<li>현재 순서 노드의 값 = 왼쪽 자식 노드의 값 * 오른쪽 자식 노드의 값</li>
<li>더 이상 분할할 수 없을 때(범위 내에 수가 하나 뿐일 때) 해당 수를 현재 순서 노드의 값으로 설정하고 반환한다.</li>
</ul>
<p><strong>4)</strong> <code>m + k</code> 만큼의 (곱 구하기, 수 바꾸기)를 실행한다.</p>
<ul>
<li><code>mul()</code> 함수를 통해 현재 순서에서 구해야하는 범위의 구간합을 구하고 이를 출력한다.<ul>
<li>범위 밖에 있는 경우 1을 반환한다.</li>
<li>범위 안에 있는 경우 현재 순서 노드의 구간곱을 반환한다.</li>
<li>범위에 걸쳐 있는 경우 현재 범위의 절반으로 나누어 각 범위에 대해 <code>mul()</code>을 실행한다.</li>
</ul>
</li>
<li><code>update()</code> 함수를 통해 특정 인덱스의 수를 다른 수로 변경한다.<ul>
<li>구간합과 달리 특정 노드의 값이 0으로 바뀌거나, 0에서 다른 값으로 바뀔 때 상위 노드부터 계산해 값을 수정할 수 없다. 따라서 <code>init()</code>과 같이 하위 노드 값부터 상위 노드로 바뀌는 방식으로 진행한다.<ul>
<li>범위 밖에 있는 경우 무시한다.</li>
<li>범위 안에 있다면 </li>
</ul>
</li>
<li>더 이상 분할할 수 없을 때(범위 내에 수가 하나 뿐일 때)는 바뀔 수(<code>dif</code>)를 현재 순서 노드의 값으로 설정하고 반환한다.</li>
<li>분할할 수 있다면 데이터 범위를 반으로 분할해 각 범위에 대해 <code>update()</code>를 실행한다.<ul>
<li><code>update()</code> 함수 실행 이후 <code>datas</code>의 특정 인덱스 값도 변경해주어야 한다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<h2 id="🖥️-풀이-코드">🖥️ 풀이 코드</h2>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;algorithm&gt;
using namespace std;

int n, m, k;
long long tree[4000004];
long long datas[1000001];
const long long MOD = 1000000007;

// 구간곱 트리 초기화
long long init(int start, int end, int node)
{
    if (start == end) return tree[node] = datas[start];
    int mid = start + (end - start) / 2;
    return tree[node] = (init(start, mid, node * 2) * init(mid + 1, end, node * 2 + 1)) % MOD;
}

// 구간곱 구하기
long long mul(int start, int end, int node, int left, int right)
{
    if (end &lt; left || right &lt; start) return 1;
    if (left &lt;= start &amp;&amp; end &lt;= right) return tree[node];
    int mid = start + (end - start) / 2;
    return (mul(start, mid, node * 2, left, right)
        * mul(mid + 1, end, node * 2 + 1, left, right)) % MOD;
}

// 특정 수 변경
long long update(int start, int end, int node, int index, long long dif)
{
    if (index &lt; start || end &lt; index) return tree[node];
    if (start == end) return tree[node] = dif;    // index가 현재 구간에 들어오고, 구간 내에 수가 하나 뿐이다 -&gt; 해당 수가 바꾸고자 하는 수이므로 해당 노드 값 변경 
    int mid = start + (end - start) / 2;
    return tree[node] = (update(start, mid, node * 2, index, dif) * update(mid + 1, end, node * 2 + 1, index, dif)) % MOD;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr); cout.tie(nullptr);

    cin &gt;&gt; n &gt;&gt; m &gt;&gt; k;
    for (int i = 1; i &lt;= n; i++)
        cin &gt;&gt; datas[i];

    init(1, n, 1);
    for (int i = 0; i &lt; m + k; i++)
    {
        int a, b, c;
        cin &gt;&gt; a &gt;&gt; b &gt;&gt; c;
        if (a == 1)
        {
            update(1, n, 1, b, c);
            datas[b] = c;
        }
        else
            cout &lt;&lt; mul(1, n, 1, b, c) &lt;&lt; &#39;\n&#39;;
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 1238번 : 파티]]></title>
            <link>https://velog.io/@doorbals_512/%EB%B0%B1%EC%A4%80-1238%EB%B2%88-%ED%8C%8C%ED%8B%B0</link>
            <guid>https://velog.io/@doorbals_512/%EB%B0%B1%EC%A4%80-1238%EB%B2%88-%ED%8C%8C%ED%8B%B0</guid>
            <pubDate>Thu, 09 Mar 2023 17:10:45 GMT</pubDate>
            <description><![CDATA[<h2 id="🔗-문제-링크">🔗 문제 링크</h2>
<p><a href="https://www.acmicpc.net/problem/1238">https://www.acmicpc.net/problem/1238</a></p>
<hr>
<h2 id="✍️-문제-풀이">✍️ 문제 풀이</h2>
<p>해당 문제는 <strong>최단 거리</strong>를 구하는 문제로, <strong>다익스트라</strong> 알고리즘을 사용하여 풀이했다.</p>
<p><strong>1)</strong> 마을 개수 <code>n</code>과 도로의 개수 <code>m</code>, 파티가 열리는 마을 <code>x</code> 를 입력 받아 저장한다. 
=&gt; 이 때 <code>n</code>은 노드 개수, <code>m</code>은 간선 개수, <code>x</code>는 시작 지점이자 도착 지점으로 볼 수 있다.</p>
<p><strong>2)</strong> (다른 도시들에서 x 도시까지 가는 거리 + x 도시에서 다른 도시들로 가는 거리)를 저장하기 위해 <code>result</code> 배열을 선언한다.</p>
<p><strong>3)</strong> 1 ~ n 도시에서 x 도시까지의 거리를 각각 구하기 위해 1 ~ n을 시작점으로하는 <strong>다익스트라</strong> 알고리즘을 각각 실행한다. (다익스트라 알고리즘 과정은 생략)</p>
<ul>
<li>각 알고리즘을 실행하고 나면 <code>dp[x]</code>에 (현재 순서(i) 도시 ~ x 도시까지의 최단 거리)가 저장이 되므로, 이를 <code>result[i]</code>에 누적한다.</li>
<li>다익스트라 알고리즘 실행 전에 매번 <code>dp</code> 배열을 <strong>INF</strong>로 초기화해준다.</li>
</ul>
<p><strong>4)</strong> x 도시에서 1 ~ n 도시까지의 거리를 구하기 위해 x를 시작점으로 하는 <strong>다익스트라</strong> 알고리즘을 1회 실행한다.</p>
<ul>
<li>알고리즘을 실행하고나면 <code>dp[1 ~ n]</code>에 각각 (x 도시에서 1 ~ n도시까지의 최단 거리)가 저장이되므로, 이를 <code>result[1 ~ n]</code>에 누적한다. </li>
<li>다익스트라 알고리즘 실행 전에 <code>dp</code> 배열을 <strong>INF</strong>로 초기화해준다.</li>
</ul>
<p><strong>5)</strong> 오고 가는 데에 시간이 가장 오래 걸리는 경우를 구해야하므로, <code>result</code> 배열에 들어있는 모든 값들을 탐색해 그 중 가장 큰 값을 출력한다.</p>
<hr>
<h2 id="🖥️-풀이-코드">🖥️ 풀이 코드</h2>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;algorithm&gt;
#include &lt;queue&gt;

using namespace std;
typedef pair&lt;int, int&gt; pii;

vector&lt;vector&lt;pii&gt;&gt; graph;
int dp[1001];
int result[1001];
int n, m, x;
const long long INF = 1e9;

void dijkstra(int start)
{
    priority_queue&lt;pii, vector&lt;pii&gt;, greater&lt;pii&gt;&gt; pq;
    pq.push(pii(0, start));
    dp[start] = 0;
    while (!pq.empty())
    {
        int dist = pq.top().first;
        int cur = pq.top().second;
        pq.pop();
        if (dp[cur] &lt; dist)    continue;
        for (int i = 0; i &lt; graph[cur].size(); i++)
        {
            pii curEdge = graph[cur][i];
            int cost = dist + curEdge.first;
            if (cost &lt; dp[curEdge.second])
            {
                dp[curEdge.second] = cost;
                pq.push(pii(cost, curEdge.second));
            }
        }
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr); cout.tie(nullptr);
    cin &gt;&gt; n &gt;&gt; m &gt;&gt; x;
    graph.resize(n + 1);
    for (int i = 0; i &lt; m; i++)
    {
        int a, b, c;
        cin &gt;&gt; a &gt;&gt; b &gt;&gt; c;
        graph[a].push_back(pii(c, b));
    }

    // 1 ~ n 도시부터 x 도시까지의 거리 구하기
    for (int i = 1; i &lt;= n; i++)
    {
        fill(dp, dp + n + 1, INF);
        dijkstra(i);
        result[i] += dp[x];
    }

    // x 도시부터 1 ~ n 도시까지의 거리 구하기
    fill(dp, dp + n + 1, INF);
    dijkstra(x);
    for (int i = 1; i &lt;= n; i++)
        result[i] += dp[i];

    int maxTime = 0;
    for (int i = 1; i &lt;= n; i++)
    {
        if (maxTime &lt; result[i])
            maxTime = result[i];
    }
    cout &lt;&lt; maxTime;
}
</code></pre>
<hr>
<h2 id="✍️-더-효율적인-풀이">✍️ 더 효율적인 풀이</h2>
<ul>
<li><p>위 코드의 경우 다익스트라 알고리즘이 n + 1회 실행된다. (다른 도시에서 x 도시까지의 최단 거리 구하기 위해 n회 + x 도시에서 다른 도시까지의 거리 구하기 위해 1회)</p>
</li>
<li><p>하지만 다른 도시에서 x 도시까지의 최단 거리를 구하는 데에 다익스트라 알고리즘을 1회만 실행할 수 있는 방법이 있다.</p>
<ul>
<li>모든 노드에서 특정 노드까지의 최단 거리</li>
<li><strong>기존 그래프의 역그래프에서 특정 노드에서 다른 모든 노드까지의 최단 거리</strong></li>
</ul>
</li>
<li><p>위 두 경우는 같은 결과값을 가지게 된다. 
첫 번째 경우는 모든 노드를 시작점으로 다익스트라 알고리즘을 n회 실행해야하지만, 두 번째 경우는 특정 노드를 시작점으로 다익스트라 알고리즘을 1회만 실행하면 되므로 시간적으로 더 효율적이다.</p>
</li>
<li><p>따라서 <strong>(기존 그래프에서 x를 시작점으로 하는 다른 모든 노드까지의 최단 거리)</strong> + <strong>(역 그래프에서 x를 시작점으로 하는 다른 모든 노드까지의 최단 거리)</strong>를 구하면 각 노드에서 x까지 갔다가 되돌아오는 거리를 구할 수 있다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/doorbals_512/post/230ea14c-b471-4597-bce5-ee6ddd7d029e/image.png" alt=""></p>
<hr>
<h2 id="🖥️-풀이-코드-1">🖥️ 풀이 코드</h2>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;algorithm&gt;
#include &lt;queue&gt;

using namespace std;
typedef pair&lt;int, int&gt; pii;

vector&lt;vector&lt;pii&gt;&gt; graph;
vector&lt;vector&lt;pii&gt;&gt; reverse_graph;
int dp[1001];
int result[1001];
int n, m, x;
const long long INF = 1e9;

void dijkstra(int start, bool reverse)
{
    priority_queue&lt;pii, vector&lt;pii&gt;, greater&lt;pii&gt;&gt; pq;
    pq.push(pii(0, start));
    dp[start] = 0;
    while (!pq.empty())
    {
        int dist = pq.top().first;
        int cur = pq.top().second;
        pq.pop();
        if (dp[cur] &lt; dist)    continue;
        if (!reverse)
        {
            for (int i = 0; i &lt; graph[cur].size(); i++)
            {
                pii curEdge = graph[cur][i];
                int cost = dist + curEdge.first;
                if (cost &lt; dp[curEdge.second])
                {
                    dp[curEdge.second] = cost;
                    pq.push(pii(cost, curEdge.second));
                }
            }
        }
        else
        {
            for (int i = 0; i &lt; reverse_graph[cur].size(); i++)
            {
                pii curEdge = reverse_graph[cur][i];
                int cost = dist + curEdge.first;
                if (cost &lt; dp[curEdge.second])
                {
                    dp[curEdge.second] = cost;
                    pq.push(pii(cost, curEdge.second));
                }
            }
        }
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr); cout.tie(nullptr);
    cin &gt;&gt; n &gt;&gt; m &gt;&gt; x;
    graph.resize(n + 1);
    reverse_graph.resize(n + 1);
    for (int i = 0; i &lt; m; i++)
    {
        int a, b, c;
        cin &gt;&gt; a &gt;&gt; b &gt;&gt; c;
        graph[a].push_back(pii(c, b));
        reverse_graph[b].push_back(pii(c, a));
    }

    // 1 ~ n 도시부터 x 도시까지의 거리 구하기
    fill(dp, dp + n + 1, INF);
    dijkstra(x, true);
    for (int i = 1; i &lt;= n; i++)
        result[i] += dp[i];

    // x 도시부터 1 ~ n 도시까지의 거리 구하기
    fill(dp, dp + n + 1, INF);
    dijkstra(x, false);
    for (int i = 1; i &lt;= n; i++)
        result[i] += dp[i];

    int maxTime = 0;
    for (int i = 1; i &lt;= n; i++)
    {
        if (maxTime &lt; result[i])
            maxTime = result[i];
    }
    cout &lt;&lt; maxTime;
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 1275번 : 커피숍2]]></title>
            <link>https://velog.io/@doorbals_512/%EB%B0%B1%EC%A4%80-1275%EB%B2%88-%EC%BB%A4%ED%94%BC%EC%88%8D2</link>
            <guid>https://velog.io/@doorbals_512/%EB%B0%B1%EC%A4%80-1275%EB%B2%88-%EC%BB%A4%ED%94%BC%EC%88%8D2</guid>
            <pubDate>Thu, 09 Mar 2023 16:34:39 GMT</pubDate>
            <description><![CDATA[<h2 id="🔗-문제-링크">🔗 문제 링크</h2>
<p><a href="https://www.acmicpc.net/problem/1275">https://www.acmicpc.net/problem/1275</a></p>
<hr>
<h2 id="✍️-문제-풀이">✍️ 문제 풀이</h2>
<p>해당 문제는 수열의 <strong>특정 구간 합</strong>을 구하는 문제로, <strong>세그먼트 트리</strong> 알고리즘을 사용하여 풀이했다.</p>
<p><strong>1)</strong> 수열을 저장할 <code>datas</code>, 구간합 트리를 저장할 <code>tree</code> 배열을 선언한다. 이 때 구간합 트리의 크기는 수열 배열의 최대 크기의 4배 이상으로 설정한다.</p>
<p><strong>2)</strong> 수의 개수 <code>n</code>과 턴의 개수 <code>q</code>를 입력 받아 저장하고, n개의 수를 입력받아 <code>datas</code>에 저장한다.</p>
<p><strong>3)</strong> <code>init()</code> 함수를 통해 구간합 트리를 초기화 한다.</p>
<ul>
<li>최상위 노드부터 시작해 데이터 범위를 반씩 분할하여 구간합을 저장하는 방식으로 트리를 채운다. 재귀를 통해 가장 작은 범위의 값을 구해, 그 값들로 더 큰 범위의 값을 구한다.</li>
<li>현재 순서 노드의 값 = 왼쪽 자식 노드의 값 + 오른쪽 자식 노드의 값</li>
<li>더 이상 분할할 수 없을 때(범위 내에 수가 하나 뿐일 때) 해당 수를 현재 순서 노드의 값으로 설정하고 반환한다.</li>
</ul>
<p><strong>4)</strong> <code>q</code> 만큼의 (합 구하기, 수 바꾸기)를 실행한다.</p>
<ul>
<li><code>sum()</code> 함수를 통해 현재 순서에서 구해야하는 범위의 구간합을 구하고 이를 출력한다.<ul>
<li>범위 밖에 있는 경우 0을 반환한다.</li>
<li>범위 안에 있는 경우 현재 순서 노드의 구간합을 반환한다.</li>
<li>범위에 걸쳐 있는 경우 현재 범위의 절반으로 나누어 합을 구한다.</li>
<li>이 때 반드시 x &lt; y 인 것은 아니므로, x &lt; y일 때와 x &gt; y일 때 매개변수를 다르게 입력한다.</li>
</ul>
</li>
<li><code>update()</code> 함수를 통해 특정 인덱스의 수를 다른 수로 변경한다.<ul>
<li>범위 밖에 있는 경우 무시한다.</li>
<li>범위 안에 있으면 현재 순서 노드에 변경 값을 더해주고, 하위 노드로 내려가며 범위 내에 있는 다른 원소도 갱신한다.</li>
<li><code>update()</code> 함수 실행 이후 <code>datas</code>의 특정 인덱스 값도 변경해주어야 한다.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="🖥️-풀이-코드">🖥️ 풀이 코드</h2>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;algorithm&gt;
using namespace std;

long long tree[400004];
long long datas[100001];
int n, q;

long long init(int start, int end, int node)
{
    if (start == end) return tree[node] = datas[start];
    int mid = (start + end) / 2;
    return tree[node] = init(start, mid, node * 2) + init(mid + 1, end, node * 2 + 1);
}

long long sum(int start, int end, int node, int left, int right)
{
    if (left &gt; end || right &lt; start)
        return 0;
    if (left &lt;= start &amp;&amp; end &lt;= right)
        return tree[node];
    int mid = (start + end) / 2;
    return sum(start, mid, node * 2, left, right)
        + sum(mid + 1 ,end, node * 2 + 1, left, right);
}

void update(int start, int end, int node, int index, long long dif)
{
    if (index &lt; start || index &gt; end) return;
    tree[node] += dif;
    if (start == end) return;
    int mid = (start + end) / 2;
    update(start, mid, node * 2, index, dif);
    update(mid + 1, end, node * 2 + 1, index, dif);
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr); cout.tie(nullptr);

    cin &gt;&gt; n &gt;&gt; q;
    for (int i = 1; i &lt;= n; i++)
        cin &gt;&gt; datas[i];

    init(1, n, 1);
    for (int i = 0; i &lt; q; i++)
    {
        int a, b, c, d;
        cin &gt;&gt; a &gt;&gt; b &gt;&gt; c &gt;&gt; d;
        if(a &lt; b)
            cout &lt;&lt; sum(1, n, 1, a, b) &lt;&lt; &#39;\n&#39;;
        else
            cout &lt;&lt; sum(1, n, 1, b, a) &lt;&lt; &#39;\n&#39;;
        long long calcNum = d - datas[c];
        update(1, n, 1, c, calcNum);
        datas[c] = d;
    }
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] 세그먼트 트리 (Segment Tree)]]></title>
            <link>https://velog.io/@doorbals_512/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%84%B8%EA%B7%B8%EB%A8%BC%ED%8A%B8-%ED%8A%B8%EB%A6%AC-Segment-Tree</link>
            <guid>https://velog.io/@doorbals_512/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%84%B8%EA%B7%B8%EB%A8%BC%ED%8A%B8-%ED%8A%B8%EB%A6%AC-Segment-Tree</guid>
            <pubDate>Tue, 07 Mar 2023 05:15:39 GMT</pubDate>
            <description><![CDATA[<h2 id="1-세그먼트-트리">1. 세그먼트 트리</h2>
<p>: 여러 개의 데이터가 연속적으로 존재할 때 특정 범위 데이터의 합을 빠르고 간단하게 구할 수 있는 자료구조</p>
<h2 id="2-배열에서-특정-구간-합-구하기">2. 배열에서 특정 구간 합 구하기</h2>
<p><strong>1)</strong> 단순 배열을 이용해 선형적으로 구하기
: 범위 내 값을 하나씩 더해가면서 구할 수 있다. 이 때 데이터 개수가 N개이면 시간 복잡도는 <strong>O(N)</strong>이 된다. 이 경우에는 구간 합을 구하는 속도가 너무 느리기 때문에 더 나은 알고리즘을 고안해야 한다.</p>
<p><strong>2)</strong> <strong>세그먼트 트리</strong>를 이용해 구하기
: 트리 구조 특성 상 N개 데이터의 합을 구할 때 시간 복잡도가 <strong>O(logN)</strong>이 된다.</p>
<h2 id="3-세그먼트-트리를-이용한-구간-합">3. 세그먼트 트리를 이용한 구간 합</h2>
<p><img src="https://velog.velcdn.com/images/doorbals_512/post/247f5156-fb23-4fbb-9c96-39e183f12acc/image.png" alt=""></p>
<p>단순 배열을 위와 같은 트리 구조로 변경했을 때 구간 합을 구하는 방법을 알아볼 것이다.</p>
<h3 id="1-구간-합-트리-생성하기">1) 구간 합 트리 생성하기</h3>
<ul>
<li><p>가장 먼저 최상단 노드에 전체 원소(0 ~ 11)를 더한 값을 삽입한다.
<img src="https://velog.velcdn.com/images/doorbals_512/post/a60fe634-5452-4c2c-84d0-316602861a2d/image.png" alt=""></p>
</li>
<li><p>이후 최상단 노드의 양쪽 자식 노드에 각각 인덱스 0 ~ 5 원소의 합, 인덱스 6 ~ 11 원소의 합을 삽입한다. 
<img src="https://velog.velcdn.com/images/doorbals_512/post/df8f8fc1-e5e8-4fce-9047-9caeda3f40f0/image.png" alt=""></p>
</li>
<li><p>위와 같이 부모 노드의 데이터 범위를 반씩 분할하여 그 구간의 합들을 저장하는 방식으로 트리를 채워 나가는 것이다. 이러한 과정을 반복하면 구간 합 트리의 전체 노드를 구할 수 있다.</p>
<ul>
<li>부모 노드의 데이터 범위를 start ~ end 라고 하면</li>
<li>부모 노드 크기를 반으로 분할 : mid = (start + end) / 2</li>
<li>자식 노드 중 왼쪽 노드는 start ~ mid / 오른쪽 노드는 mid + 1 ~ end
<img src="https://velog.velcdn.com/images/doorbals_512/post/443be244-db15-45ef-b8e1-d577175c0125/image.png" alt=""></li>
</ul>
</li>
<li><p>구간 합 트리를 만들 때는 <strong>인덱스 번호를 1로 시작</strong>한다.</p>
<ul>
<li>데이터의 인덱스는 0부터 시작하지만, 구간 합 트리를 1부터 시작하면 부모 노드 번호에 2를 곱하면 왼쪽 자식 노드를 가리키므로 인덱스를 설정하기 편하기 때문이다.</li>
</ul>
</li>
<li><p>데이터의 개수가 N개일 때, 구간 합 트리는 <strong>N보다 큰 가장 가까운 제곱수를 구한 뒤, 그 수의 2배의 크기</strong>를 가져야 한다.</p>
<ul>
<li>ex. 데이터의 개수가 12개라면, 12보다 큰 가장 가까운 제곱수는 16</li>
<li>16 x 2 = 32이므로 구간 합 트리의 크기는 32이다.</li>
<li>계산하기 귀찮다면 데이터 개수 x 4를 해주면 메모리를 조금 더 먹지만 편리하게 계산 가능하다.</li>
</ul>
</li>
</ul>
<h4 id="🖥️-예제-코드">🖥️ 예제 코드</h4>
<pre><code class="language-cpp">int tree[32];
int datas[12];

// start : 범위 시작 / end : 범위 끝 / node : 현재 구간합 트리 노드 번호
int init(int start, int end, int node)
{
    // 범위에 수가 하나 남을 때까지 분할하고, 하나 남으면 해당 수 반환
    if (start == end) return tree[node] = datas[start];
    int mid = (start + end) / 2;
    // 현재 노드의 범위를 재귀적으로 반으로 분할하여 두 부분의 합을 자기 자신으로 할당
    return tree[node] = init(start, mid, node * 2) + init(mid + 1, end, node * 2 + 1);
}</code></pre>
<h3 id="2-구간-합을-구하는-함수-만들기">2) 구간 합을 구하는 함수 만들기</h3>
<p><img src="https://velog.velcdn.com/images/doorbals_512/post/ae326548-9b56-4a7a-be05-ecc91f4fa1a8/image.png" alt=""></p>
<ul>
<li>만약 4 ~ 8 범위에 대한 합을 구한다고 하면 색칠 된 노드들의 값만 더해주면 된다.</li>
</ul>
<h4 id="🖥️-예제-코드-1">🖥️ 예제 코드</h4>
<pre><code class="language-cpp">// 범위 안에 있는 경우에 한해서만 더해주면 된다.
// start : 범위 시작 / end : 범위 끝 // node : 현재 구간합 트리 노드 번호
// left : 구하고자 하는 범위 시작 / right : 구하고자 하는 범위 끝
int sum(int start, int end, int node, int left, int right)
{
    // 범위 밖에 있는 경우
    if (left &gt; end || right &lt; start) return 0;
    // 범위 안에 있는 경우
    if (left &lt;= start &amp;&amp; end &lt;= right) return tree[node];
    // 범위에 걸쳐있는 경우 두 부분으로 나누어 합 구하기
    int mid = (start + end) / 2;
    return sum(start, mid, node * 2, left, right) 
        + sum(mid + 1, end, node * 2 + 1, left, right);
}</code></pre>
<h3 id="3-특정-원소-값을-수정하는-함수-만들기">3) 특정 원소 값을 수정하는 함수 만들기</h3>
<p><img src="https://velog.velcdn.com/images/doorbals_512/post/f6297911-00c2-463a-b576-58042f30869e/image.png" alt=""></p>
<ul>
<li>특정 원소 값을 수정할 때는 해당 원소를 포함하고 있는 모든 구간 합 노드들을 갱신해준다.</li>
<li>만약 인덱스 7의 노드를 수정한다고 하면 색칠 된 노드들의 값만 수정하면 된다.</li>
</ul>
<h4 id="🖥️-예제-코드-2">🖥️ 예제 코드</h4>
<pre><code class="language-cpp">// index : 값을 수정하고자 하는 노드 / dif : 수정할 값
void update(int start, int end, int node, int index, int dif)
{
    // 범위 밖에 있는 경우
    if (index &lt; start || index &gt; end) return;
    // 범위 안에 있으면 내려가며 다른 원소도 갱신
    tree[node] += dif;
    if (start == end) return;
    int mid = (start + end) / 2;
    update(start, mid, node * 2, index, dif);
    update(mid + 1, end, node * 2 + 1, index, dif);
}</code></pre>
<h3 id="4-모든-과정-취합-코드">4) 모든 과정 취합 코드</h3>
<pre><code class="language-cpp">int tree[32];
int datas[12] = { 1, 9, 3, 8, 4, 5, 5, 9, 10, 3, 4, 5 };

// start : 범위 시작 / end : 범위 끝 / node : 현재 구간합 트리 노드 번호
int init(int start, int end, int node)
{
    // 범위에 수가 하나 남을 때까지 분할하고, 하나 남으면 해당 수 반환
    if (start == end) return tree[node] = datas[start];
    int mid = (start + end) / 2;
    // 현재 노드의 범위를 재귀적으로 반으로 분할하여 두 부분의 합을 자기 자신으로 할당
    return tree[node] = init(start, mid, node * 2) + init(mid + 1, end, node * 2 + 1);
}

// 범위 안에 있는 경우에 한해서만 더해주면 된다.
// start : 범위 시작 / end : 범위 끝 // node : 현재 구간합 트리 노드 번호
// left : 구하고자 하는 범위 시작 / right : 구하고자 하는 범위 끝
int sum(int start, int end, int node, int left, int right)
{
    // 범위 밖에 있는 경우
    if (left &gt; end || right &lt; start) return 0;
    // 범위 안에 있는 경우
    if (left &lt;= start &amp;&amp; end &lt;= right) return tree[node];
    // 범위에 걸쳐있는 경우 두 부분으로 나누어 합 구하기
    int mid = (start + end) / 2;
    return sum(start, mid, node * 2, left, right) 
        + sum(mid + 1, end, node * 2 + 1, left, right);
}

// index : 값을 수정하고자 하는 노드 / dif : 수정할 값
void update(int start, int end, int node, int index, int dif)
{
    // 범위 밖에 있는 경우
    if (index &lt; start || index &gt; end) return;
    // 범위 안에 있으면 내려가며 다른 원소도 갱신
    tree[node] += dif;
    if (start == end) return;
    int mid = (start + end) / 2;
    update(start, mid, node * 2, index, dif);
    update(mid + 1, end, node * 2 + 1, index, dif);
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr); cout.tie(nullptr);

    init(0, 11, 1);
    // 구간 합 구하기
    cout &lt;&lt; &quot;0 ~ 12 구간 합 : &quot; &lt;&lt; sum(0, 11, 1, 0, 11) &lt;&lt; endl;
    cout &lt;&lt; &quot;4 ~ 8 구간 합 : &quot; &lt;&lt; sum(0, 11, 1, 4, 8) &lt;&lt; endl;

    // 특정 원소 값 수정하기
    cout &lt;&lt; &quot;인덱스 5의 원소 -5만큼 수정&quot; &lt;&lt; endl;
    update(0, 11, 1, 5, -5);

    // 구간 합 구하기
    cout &lt;&lt; &quot;0 ~ 12 구간 합 : &quot; &lt;&lt; sum(0, 11, 1, 0, 11) &lt;&lt; endl;
}

[출력 결과]
0 ~ 12 구간 합 : 66
4 ~ 8 구간 합 : 33
인덱스 5의 원소 -5만큼 수정
0 ~ 12 구간 합 : 61</code></pre>
<p><strong>👁️‍🗨️ 참고</strong>
<a href="https://m.blog.naver.com/ndb796/221282210534">https://m.blog.naver.com/ndb796/221282210534</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 수식 최대화 - LEVEL 2]]></title>
            <link>https://velog.io/@doorbals_512/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%88%98%EC%8B%9D-%EC%B5%9C%EB%8C%80%ED%99%94-LEVEL-2</link>
            <guid>https://velog.io/@doorbals_512/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%88%98%EC%8B%9D-%EC%B5%9C%EB%8C%80%ED%99%94-LEVEL-2</guid>
            <pubDate>Mon, 06 Mar 2023 08:27:54 GMT</pubDate>
            <description><![CDATA[<h2 id="🔗-문제-링크">🔗 문제 링크</h2>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/67257">https://school.programmers.co.kr/learn/courses/30/lessons/67257</a></p>
<hr>
<h2 id="✍️-문제-풀이">✍️ 문제 풀이</h2>
<p>해당 문제는 단순 구현 문제로, 문자열 처리가 중점인 문제이다.</p>
<p><strong>1)</strong> 입력받은 <code>expression</code> 문자열을 각 숫자와 연산자로 따로 나누어 저장한다.</p>
<ul>
<li><code>split()</code> 함수 : 인수로 전달된 문자열을 숫자와 연산자로 나누어 같은 벡터에 순서대로 저장한다.<ul>
<li>문자열 내에서 &#39;*&#39;, &#39;+&#39;, &#39;-&#39; 를 각각 find 하여 그 인덱스(위치)가 가장 작은 연산자 (가장 앞에 있는 것)를 찾는다.</li>
<li>해당 연산자를 기준으로 앞에 있는 문자열, 해당 연산자를 각각 <code>result</code> 벡터에 집어넣고, 기존 문자열을 연산자 뒤의 문자열로 교체한다.</li>
<li>위 과정을 더 이상 연산자가 남지 않을 때까지 반복하고, 마지막 남은 문자열을 <code>result</code> 벡터에 집어넣고 <code>result</code>를 반환한다.</li>
</ul>
</li>
<li>리스트 <code>nums</code>와 <code>op</code>를 선언하고, <code>split()</code> 함수에서 반환된 벡터를 확인하여 숫자 문자열은 <code>nums</code>에, 연산자는 <code>op</code>에 삽입한다.</li>
</ul>
<p><strong>2)</strong> 2차원 벡터 <code>orders</code>를 선언해 3개의 연산자로 만들 수 있는 우선순위 6개를 저장한다. </p>
<p><strong>3)</strong> <code>orders</code>에 저장된 각각의 연산자 우선순위에 따라 주어진 문자열을 계산한다.</p>
<ul>
<li><code>nums</code>와 <code>op</code>를 <code>numsCopy</code>, <code>opCopy</code>에 복사해 저장한다.</li>
<li>현재 순서 <code>orders</code>에 저장된 연산자들을 순서대로 적용하여 아래 과정을 반복한다.<ul>
<li><code>opCopy</code>에 저장된 각 연산자를 차례대로 확인한다.<ul>
<li>만약 해당 연산자가 <code>orders</code>의 현재 순서 연산자가 아니라면 다음 연산자로 넘어간다.</li>
<li>현재 순서 연산자라면 <code>opCopy</code>의 끝 인덱스까지 확인할 때까지 아래 과정을 반복한다.<ul>
<li><code>numsCopy</code>에 저장된 해당 연산자의 앞과 뒤에 있는 숫자들을 해당 연산자로 계산해 
, 원래 <code>numsCopy</code>에 앞에 있는 숫자의 위치에 저장한다.</li>
<li><code>opCopy</code>의 해당 연산자와, <code>numsCopy</code>의 뒤에 있는 숫자를 삭제한다. 
(리스트이므로 자동으로 당겨짐)</li>
<li><code>opCopy</code>의 맨 앞으로 돌아간다.</li>
</ul>
</li>
<li><code>opCopy</code>의 끝까지 확인했다면 <code>orders</code>의 다음 순서 연산자로 넘어간다.</li>
</ul>
</li>
</ul>
</li>
<li>위 과정이 완료되면 <code>numsCopy</code>에 계산 완료된 숫자 하나만이 남는다. 이 숫자를 <code>result</code> 벡터에 삽입한다.</li>
</ul>
<p><strong>4)</strong> 6개의 우선순위를 각각 적용하여 계산된 값들이 최종적으로 <code>result</code>에 저장되고, 이 값들의 절대값들을 비교하여 가장 큰 값을 출력한다. </p>
<hr>
<h2 id="🖥️-풀이-코드">🖥️ 풀이 코드</h2>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;algorithm&gt;
#include &lt;cmath&gt;
#include &lt;string&gt;
#include &lt;list&gt;
using namespace std;

// 문자열을 각 연산자를 기준으로 나누어 숫자와 연산자로 저장
vector&lt;string&gt; split(string str)
{
    char dlim1 = &#39;*&#39;, dlim2 = &#39;+&#39;, dlim3 = &#39;-&#39;;
    vector&lt;string&gt; result;
    int a, b, c;

    while (true)
    {
        a = find(str.begin(), str.end(), dlim1) - str.begin();
        b = find(str.begin(), str.end(), dlim2) - str.begin();
        c = find(str.begin(), str.end(), dlim3) - str.begin();

        if (a == str.size() &amp;&amp; b == str.size() &amp;&amp; c == str.size())
        {
            result.push_back(str);
            return result;
        }            
        else
        {
            int dlim = min({ a, b, c });
            result.push_back(str.substr(0, dlim));
            result.push_back(str.substr(dlim, 1));
            str = str.substr(dlim + 1, str.size() - dlim);
        }
    }
}

long long solution(string expression)
{
    // 연산자는 op, 숫자는 nums에 저장
    vector&lt;string&gt; v = split(expression);
    list&lt;string&gt; nums, op;
    for (int i = 0; i &lt; v.size(); i++)
    {
        if (v[i] == &quot;*&quot; || v[i] == &quot;+&quot; || v[i] == &quot;-&quot;)
            op.push_back(v[i]);
        else
            nums.push_back(v[i]);
    }

    vector&lt;vector&lt;string&gt;&gt; orders;
    orders.push_back({ &quot;*&quot;, &quot;+&quot;, &quot;-&quot; });
    orders.push_back({ &quot;*&quot;, &quot;-&quot;, &quot;+&quot; });
    orders.push_back({ &quot;+&quot;, &quot;*&quot;, &quot;-&quot; });
    orders.push_back({ &quot;+&quot;, &quot;-&quot;, &quot;*&quot; });
    orders.push_back({ &quot;-&quot;, &quot;+&quot;, &quot;*&quot; });
    orders.push_back({ &quot;-&quot;, &quot;*&quot;, &quot;+&quot; });

    vector&lt;long long&gt; result;
    for (int i = 0; i &lt; orders.size(); i++)
    {
        list&lt;string&gt; numsCopy = nums, opCopy = op;
        for (int k = 0; k &lt; 3; k++)
        {
            int j = 0;
            while(true)
            {
                if (j &gt;= opCopy.size())
                    break;

                auto it = opCopy.begin();                // 연산자 이터레이터
                advance(it, j);
                if (*it != orders[i][k])
                {
                    j++;
                    continue;
                }

                auto prevNumsIt = numsCopy.begin();        // 연산자 이전 글자 이터레이터
                auto postNumsIt = numsCopy.begin();        // 연산자 다음 글자 이터레이터
                advance(prevNumsIt, j);
                advance(postNumsIt, j + 1);

                if (*it == &quot;*&quot;)
                    *prevNumsIt = to_string(stoll(*prevNumsIt) * stoll(*postNumsIt));
                else if (*it == &quot;+&quot;)
                    *prevNumsIt = to_string(stoll(*prevNumsIt) + stoll(*postNumsIt));
                else
                    *prevNumsIt = to_string(stoll(*prevNumsIt) - stoll(*postNumsIt));
                opCopy.erase(it);
                numsCopy.erase(postNumsIt);
                j = 0;
            }
        }
        result.push_back(stoll(*numsCopy.begin()));
    }

    long long maxValue = 0;
    for (int i = 0; i &lt; result.size(); i++)
    {
        long long cur = llabs(result[i]);
        if (cur &gt; maxValue)
            maxValue = cur;
    }
    return maxValue;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 14950번 : 정복자]]></title>
            <link>https://velog.io/@doorbals_512/%EB%B0%B1%EC%A4%80-14950%EB%B2%88-%EC%A0%95%EB%B3%B5%EC%9E%90</link>
            <guid>https://velog.io/@doorbals_512/%EB%B0%B1%EC%A4%80-14950%EB%B2%88-%EC%A0%95%EB%B3%B5%EC%9E%90</guid>
            <pubDate>Mon, 06 Mar 2023 07:50:41 GMT</pubDate>
            <description><![CDATA[<h2 id="🔗-문제-링크">🔗 문제 링크</h2>
<p><a href="https://www.acmicpc.net/problem/14950">https://www.acmicpc.net/problem/14950</a></p>
<hr>
<h2 id="✍️-문제-풀이">✍️ 문제 풀이</h2>
<p>해당 문제는 <strong>최소 신장 트리(MST)</strong> 문제로, 모든 노드(== 도시)에서 다른 모든 노드로 향하는 간선이 있다고 가정하고, 이 간선들에 대해 <strong>크루스칼 알고리즘</strong>을 사용하여 모든 노드들이 이어지되, 이어지는 간선들의 가중치의 합이 최소가 되는 경우를 구해야 한다. 단, 도시가 하나 이어질 때마다 가중치가 증가하는 부분을 고려해야 한다.</p>
<p><strong>1)</strong> 모든 노드들 사이에 존재하는 간선에 대한 tuple(두 노드 간 가중치, 노드A 번호, 노드B 번호)들을 저장하는 벡터인 <code>edges</code>에 모든 노드에서 다른 모든 노드로 향하는 간선에 대한 가중치를 계산해 저장한다.</p>
<p><strong>2)</strong> <code>edges</code> 벡터에 대해 <strong>크루스칼 알고리즘</strong>을 적용한다.</p>
<ul>
<li><code>edges</code> 벡터를 가중치의 오름차순으로 정렬한다.</li>
<li>모든 노드들에 대한 사이클 테이블이 자기 자신을 가리키도록 한다.</li>
<li>간선들을 차례대로 확인하며 아래 과정을 반복한다.<ul>
<li>간선에 연결된 노드A와 노드B가 같은 부모를 갖는지(같은 그래프에 포함되는지) 확인한다. <strong>(== find)</strong></li>
<li>만약 같은 부모를 갖지 않는다면 두 노드의 부모를 같게 합친다. <strong>(== union)</strong></li>
<li><code>sum</code>에 해당 간선의 가중치 + <strong>(<code>t</code> * <code>count</code>)</strong>를 누적한다.<ul>
<li><code>sum</code> : mst를 이루는 모든 간선의 가중치의 합</li>
<li><code>count</code> : 지금까지 정복한 도시의 개수</li>
<li>도시가 하나 정복될 때마다 다른 도시의 비용이 t만큼 증가하기 때문에, 다음 도시를 정복할 때 (지금까지 정복된 도시의 개수 * t)만큼 더 더해주어야 한다.</li>
</ul>
</li>
<li><code>count</code>를 하나 증가시킨다.</li>
</ul>
</li>
</ul>
<p><strong>3)</strong> 위 크루스칼 알고리즘 과정이 끝나면 <code>sum</code>에 모든 도시를 정복하는 데에 필요한 최소 비용이 저장된다.</p>
<hr>
<h2 id="🖥️-풀이-코드">🖥️ 풀이 코드</h2>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;algorithm&gt;
#include &lt;queue&gt;
#include &lt;tuple&gt;
using namespace std;
typedef tuple&lt;int, int, int&gt; tiii;

int n, m, t;
vector&lt;tiii&gt; edges;
int parent[10001];

int getParent(int n)
{
    if (parent[n] == n)
        return n;
    else
        return parent[n] = getParent(parent[n]);
}

void unionParent(int a, int b)
{
    a = getParent(a);
    b = getParent(b);

    if (a &lt; b)
        parent[b] = a;
    else
        parent[a] = b;
}

bool findParent(int a, int b)
{
    a = getParent(a);
    b = getParent(b);

    return (a == b);
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr); cout.tie(nullptr);

    cin &gt;&gt; n &gt;&gt; m &gt;&gt; t;
    for (int i = 0; i &lt; m; i++)
    {
        int a, b, c;
        cin &gt;&gt; a &gt;&gt; b &gt;&gt; c;
        edges.push_back(tiii(c, a, b));
        edges.push_back(tiii(c, b, a));
    }

    sort(edges.begin(), edges.end());
    for (int i = 1; i &lt;= n; i++)
        parent[i] = i;

    int sum = 0;
    int count = 0;
    for (int i = 0; i &lt; edges.size(); i++)
    {
        int curA = get&lt;1&gt;(edges[i]);
        int curB = get&lt;2&gt;(edges[i]);

        if (!findParent(curA, curB))
        {
            unionParent(curA, curB);
            sum += get&lt;0&gt;(edges[i]);
            sum += t * count;
            count++;
        }
    }
    cout &lt;&lt; sum;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 13549번 : 숨바꼭질 3]]></title>
            <link>https://velog.io/@doorbals_512/%EB%B0%B1%EC%A4%80-13549%EB%B2%88-%EC%88%A8%EB%B0%94%EA%BC%AD%EC%A7%88</link>
            <guid>https://velog.io/@doorbals_512/%EB%B0%B1%EC%A4%80-13549%EB%B2%88-%EC%88%A8%EB%B0%94%EA%BC%AD%EC%A7%88</guid>
            <pubDate>Sun, 05 Mar 2023 18:47:55 GMT</pubDate>
            <description><![CDATA[<h2 id="🔗-문제-링크">🔗 문제 링크</h2>
<p><a href="https://www.acmicpc.net/problem/13549">https://www.acmicpc.net/problem/13549</a></p>
<hr>
<h2 id="✍️-문제-풀이">✍️ 문제 풀이</h2>
<p>해당 문제는 <strong>최단 거리</strong>를 구하는 문제로, <strong>다익스트라</strong> 알고리즘을 사용하여 풀이했다.</p>
<p><strong>1)</strong> 수빈이의 현재 위치 <code>n</code>과 동생의 위치 <code>k</code>를 입력 받아 저장한다. 
=&gt; 이 때 <code>n</code>은 시작점, <code>k</code>는 목표 지점, 각 위치 지점들을 노드로 볼 수 있다. </p>
<p><strong>2)</strong> 시작점에서 각 위치까지 최단 거리를 저장하는 배열 <code>dp</code>를 선언하고, <code>dp</code>의 모든 값을 <strong>무한대</strong>로 초기화한다.</p>
<p><strong>3)</strong> 다익스트라 알고리즘을 실행하여 시작점에서부터 각 위치까지의 최단 거리를 구한다.</p>
<ul>
<li><strong>최소힙</strong>으로 구현하기 위해 <strong>우선순위 큐</strong> <code>pq</code>를 선언하고, 오름차순으로 정렬되도록 한다.<ul>
<li><code>pq</code>에는 노드 정보 (해당 노드까지 최소 거리, 노드 번호)가 저장된다.</li>
</ul>
</li>
<li><code>pq</code>에 (0, 시작 위치)를 삽입한다.</li>
<li>dp[start](시작점에서부터 시작점까지의 최단 거리)를 0으로 초기화한다.</li>
<li><code>pq</code>가 빌 때까지 아래 과정을 반복한다.<ul>
<li>pq에 저장되어 있는 노드 중 가중치가 가장 작은 노드에 대한 정보를 꺼낸다.<ul>
<li><code>dist</code> : 현재 노드까지의 거리</li>
<li><code>cur</code> : 현재 노드의 번호</li>
</ul>
</li>
<li>만약 dp[cur] &lt; dist라면 이미 최단 거리가 구해진 노드이므로 무시한다.</li>
<li>위 경우가 아니라면 아래 과정을 반복한다.<ul>
<li>해당 문제는 미리 간선 정보가 제공되어 저장하고 시작하는 것이 아니고, 
현재 위치에서 <strong>1, 1, 0</strong>의 가중치로 <strong>-1, +1, *2</strong> 위치에 이동이 가능하도록 되어있다. 즉, <strong>이동했을 때의 위치가 현재 위치의 인접 노드</strong>라고 보는 것이다.</li>
<li>따라서 <code>cost</code>에 현재 노드까지의 거리 + 현재 노드 ~ 이동했을 때 거리를 저장한다.</li>
<li>만약 이동 했을 때 수빈이가 <strong>있을 수 있는 범위(0 이상 100000 이하)에 위치</strong>하고, <strong>cost가 저장되어있던 이동 위치의 dp보다 작다면</strong> (cost &lt; dp[인접 노드])<ul>
<li>인접 노드의 dp를 cost로 갱신한다.</li>
<li>갱신된 인접 노드 정보 (인접 노드까지의 최소 거리, 노드 번호)를 <code>pq</code>에 삽입한다.</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><strong>4)</strong> 위 과정이 끝나면 <code>dp</code>에는 시작점부터 다른 노드까지의 최소 거리들이 저장된다.
이 때, 동생의 위치 <code>k</code>까지의 최단 거리를 알고 싶은 것이기 때문에 <strong>dp[k]</strong>를 출력한다.</p>
<hr>
<h2 id="🖥️-풀이-코드">🖥️ 풀이 코드</h2>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;algorithm&gt;
#include &lt;queue&gt;
#include &lt;tuple&gt;
using namespace std;
typedef pair&lt;int, int&gt; pii;

int n, k;
int dp[100001];
const long long INF = 1e9;

pii move(int cur, int i)
{
    if (i == 0)
        return pii(cur - 1, 1);
    else if (i == 1)
        return pii(cur + 1, 1);
    else
        return pii(cur * 2, 0);
}

void dijkstra(int start)
{
    priority_queue&lt;pii, vector&lt;pii&gt;, greater&lt;pii&gt;&gt; pq;
    pq.push(pii(0, start));
    dp[start] = 0;

    while (!pq.empty())
    {
        int dist = pq.top().first;
        int cur = pq.top().second;
        pq.pop();
        if (dp[cur] &lt; dist) continue;
        for (int i = 0; i &lt; 3; i++)
        {
            pii curEdge = move(cur, i);
            int cost = dist + curEdge.second;
            if (curEdge.first &gt;= 0 &amp;&amp; curEdge.first &lt;= 100000 &amp;&amp; cost &lt; dp[curEdge.first])
            {
                dp[curEdge.first] = cost;
                pq.push(pii(cost, curEdge.first));
            }
        }
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr); cout.tie(nullptr);

    cin &gt;&gt; n &gt;&gt; k;
    fill(dp, dp + 100001, INF);
    dijkstra(n);
    cout &lt;&lt; dp[k];
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 5972번 : 택배 배송]]></title>
            <link>https://velog.io/@doorbals_512/%EB%B0%B1%EC%A4%80-5972%EB%B2%88-%ED%83%9D%EB%B0%B0-%EB%B0%B0%EC%86%A1</link>
            <guid>https://velog.io/@doorbals_512/%EB%B0%B1%EC%A4%80-5972%EB%B2%88-%ED%83%9D%EB%B0%B0-%EB%B0%B0%EC%86%A1</guid>
            <pubDate>Sun, 05 Mar 2023 18:28:16 GMT</pubDate>
            <description><![CDATA[<h2 id="🔗-문제-링크">🔗 문제 링크</h2>
<p><a href="https://www.acmicpc.net/problem/5972">https://www.acmicpc.net/problem/5972</a></p>
<hr>
<h2 id="✍️-문제-풀이">✍️ 문제 풀이</h2>
<p>해당 문제는 <strong>최단 거리</strong>를 구하는 문제로, <strong>다익스트라</strong> 알고리즘을 사용하여 풀이했다.</p>
<p><strong>1)</strong> 헛간 개수 <code>n</code>과 길의 개수 <code>m</code>을 입력 받아 저장한다. 
이 때, 헛간 개수 == 노드 개수 / 길의 개수 == 간선 개수 라고 볼 수 있다.</p>
<p><strong>2)</strong> 2차원 벡터 <code>graph</code>를 선언하고 n + 1 크기로 초기화 한다. 그리고 m개의 길에 대한 정보 pair(헛간 번호, 소의 마리수(가중치))를 입력 받아 <code>graph</code>에 저장한다. 이 때, 헛간끼리 이어진 길은 양방향으로 이동 가능하므로 <strong>간선 저장 시 양방향으로 이어준다.</strong></p>
<p><strong>3)</strong> 시작점인 1에서 각 헛간까지 최단 거리를 저장하는 배열 <code>dp</code>를 선언하고, <code>dp</code>의 모든 값을 <strong>무한대</strong>로 초기화한다.</p>
<p><strong>4)</strong> 다익스트라 알고리즘을 실행하여 시작점에서부터 각 위치까지의 최단 거리를 구한다.</p>
<ul>
<li><strong>최소힙</strong>으로 구현하기 위해 <strong>우선순위 큐</strong> <code>pq</code>를 선언하고, 오름차순으로 정렬되도록 한다.<ul>
<li><code>pq</code>에는 노드 정보 (해당 노드까지 최소 거리, 노드 번호)가 저장된다.</li>
</ul>
</li>
<li><code>pq</code>에 (0, 시작 위치)를 삽입한다.</li>
<li>dp[start](시작점에서부터 시작점까지의 최단 거리)를 0으로 초기화한다.</li>
<li><code>pq</code>가 빌 때까지 아래 과정을 반복한다.<ul>
<li>pq에 저장되어 있는 노드 중 가중치가 가장 작은 노드에 대한 정보를 꺼낸다.<ul>
<li><code>dist</code> : 현재 노드까지의 거리</li>
<li><code>cur</code> : 현재 노드의 번호</li>
</ul>
</li>
<li>만약 dp[cur] &lt; dist라면 이미 최단 거리가 구해진 노드이므로 무시한다.</li>
<li>위 경우가 아니라면 해당 노드의 인접 노드들에 대해 아래 과정을 반복한다.<ul>
<li><code>cost</code> : 현재 노드까지의 거리 + 현재 노드 ~ 인접 노드까지의 거리</li>
<li>만약 <strong>cost가 저장되어있던 인접 노드의 dp보다 작다면</strong> (cost &lt; dp[인접 노드])<ul>
<li>인접 노드의 dp를 cost로 갱신한다.</li>
<li>갱신된 인접 노드 정보 (인접 노드까지 최소 거리, 노드 번호)를 <code>pq</code>에 삽입한다.</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><strong>4)</strong> 위 과정이 끝나면 <code>dp</code>에는 시작점부터 다른 노드까지의 최소 거리들이 저장된다.
이 때, 헛간 n까지의 최단 거리를 알고 싶은 것이기 때문에 <strong>dp[n]</strong>을 출력한다.</p>
<hr>
<h2 id="🖥️-풀이-코드">🖥️ 풀이 코드</h2>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;algorithm&gt;
#include &lt;queue&gt;
using namespace std;
typedef pair&lt;int, int&gt; pii;

int n, m;
vector&lt;vector&lt;pii&gt;&gt; graph;
int dp[50001];
const long long INF = 1e9;

void dijkstra(int start)
{
    priority_queue&lt;pii, vector&lt;pii&gt;, greater&lt;pii&gt;&gt; pq;
    pq.push(pii(0, start));
    dp[start] = 0;

    while (!pq.empty())
    {
        int dist = pq.top().first;
        int cur = pq.top().second;
        pq.pop();
        if (dp[cur] &lt; dist) continue;
        for (int i = 0; i &lt; graph[cur].size(); i++)
        {
            int cost = dist + graph[cur][i].second;
            if (cost &lt; dp[graph[cur][i].first])
            {
                dp[graph[cur][i].first] = cost;
                pq.push(pii(cost, graph[cur][i].first));
            }
        }
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr); cout.tie(nullptr);

    cin &gt;&gt; n &gt;&gt; m;
    graph.resize(n + 1);

    for (int i = 0; i &lt; m; i++)
    {
        int a, b, c;
        cin &gt;&gt; a &gt;&gt; b &gt;&gt; c;
        // 양방향 간선이므로 양쪽 노드에 모두 추가
        graph[a].push_back(pii(b, c));
        graph[b].push_back(pii(a, c));
    }

    fill(dp, dp + n + 1, INF);
    dijkstra(1);

    cout &lt;&lt; dp[n];
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 1446번 : 지름길]]></title>
            <link>https://velog.io/@doorbals_512/%EB%B0%B1%EC%A4%80-1446%EB%B2%88-%EC%A7%80%EB%A6%84%EA%B8%B8</link>
            <guid>https://velog.io/@doorbals_512/%EB%B0%B1%EC%A4%80-1446%EB%B2%88-%EC%A7%80%EB%A6%84%EA%B8%B8</guid>
            <pubDate>Sun, 05 Mar 2023 18:19:52 GMT</pubDate>
            <description><![CDATA[<h2 id="🔗-문제-링크">🔗 문제 링크</h2>
<p><a href="https://www.acmicpc.net/problem/1446">https://www.acmicpc.net/problem/1446</a></p>
<hr>
<h2 id="✍️-문제-풀이">✍️ 문제 풀이</h2>
<p>해당 문제는 <strong>최단 거리</strong>를 구하는 문제로, <strong>다익스트라</strong> 알고리즘을 사용하여 풀이했다.</p>
<p><strong>1)</strong> 지름길의 개수 <code>m</code>과 고속도로의 길이 <code>n</code>을 입력 받아 저장한다. 
=&gt; 여기서 고속도로 길이는 노드의 개수와 같다.</p>
<p><strong>2)</strong> 2차원 벡터 <code>graph</code>를 선언하고 n + 1 크기로 초기화 한다. 그리고 m개의 지름길에 대한 정보 pair(상대 노드 번호, 가중치)를 입력 받아 <code>graph</code>에 저장한다. 또한 <strong>0 ~ n까지 각 위치에서 +1 위치로 가중치 1의 간선을 연결한다.</strong></p>
<p><strong>3)</strong> 시작점에서 각 위치까지 최단 거리를 저장하는 배열 <code>dp</code>를 선언하고, <code>dp</code>의 모든 값을 <strong>무한대</strong>로 초기화한다.</p>
<p><strong>4)</strong> 다익스트라 알고리즘을 실행하여 시작점에서부터 각 위치까지의 최단 거리를 구한다.</p>
<ul>
<li><strong>최소힙</strong>으로 구현하기 위해 <strong>우선순위 큐</strong> <code>pq</code>를 선언하고, 오름차순으로 정렬되도록 한다.<ul>
<li><code>pq</code>에는 노드 정보 (해당 노드까지 최소 거리, 노드 번호)가 저장된다.</li>
</ul>
</li>
<li><code>pq</code>에 (0, 시작 위치)를 삽입한다.</li>
<li>dp[start](시작점에서부터 시작점까지의 최단 거리)를 0으로 초기화한다.</li>
<li><code>pq</code>가 빌 때까지 아래 과정을 반복한다.<ul>
<li>pq에 저장되어 있는 노드 중 가중치가 가장 작은 노드에 대한 정보를 꺼낸다.<ul>
<li><code>dist</code> : 현재 노드까지의 거리</li>
<li><code>cur</code> : 현재 노드의 번호</li>
</ul>
</li>
<li>만약 dp[cur] &lt; dist라면 이미 최단 거리가 구해진 노드이므로 무시한다.</li>
<li>위 경우가 아니라면 해당 노드의 인접 노드들에 대해 아래 과정을 반복한다.<ul>
<li><code>cost</code> : 현재 노드까지의 거리 + 현재 노드 ~ 인접 노드까지의 거리</li>
<li>만약 <strong>cost가 저장되어있던 인접 노드의 dp보다 작다면</strong> (cost &lt; dp[인접 노드])<ul>
<li>인접 노드의 dp를 cost로 갱신한다.</li>
<li>갱신된 인접 노드 정보 (인접 노드까지 최소 거리, 노드 번호)를 <code>pq</code>에 삽입한다.</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><strong>4)</strong> 위 과정이 끝나면 <code>dp</code>에는 시작점부터 다른 노드까지의 최소 거리들이 저장된다.
이 때, 0 ~ n이 현재 위치라고 가정할 때, <strong>현재 위치까지의 최소 거리 + (목적지 - 현재 위치)</strong>를 전부 비교하여 가장 작은 값을 출력한다.</p>
<hr>
<h2 id="🖥️-풀이-코드">🖥️ 풀이 코드</h2>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;algorithm&gt;
#include &lt;queue&gt;
using namespace std;
typedef pair&lt;int, int&gt; pii;

int n, m;
vector&lt;vector&lt;pii&gt;&gt; graph;    // 0 ~ n까지 각 위치와 연결된 다른 위치들의 pii(인덱스, 거리)를 저장 
int dp[10001];                
const long long INF = 1e9;

void dijkstra(int start)
{
    priority_queue&lt;pii, vector&lt;pii&gt;, greater&lt;pii&gt;&gt; pq;
    pq.push(pii(0, start));
    dp[start] = 0;

    while (!pq.empty())
    {
        int dist = pq.top().first;
        int cur = pq.top().second;
        pq.pop();
        if (dp[cur] &lt; dist) continue;
        for (int i = 0; i &lt; graph[cur].size(); i++)
        {
            int cost = dist + graph[cur][i].second;
            if (cost &lt; dp[graph[cur][i].first])
            {
                dp[graph[cur][i].first] = cost;
                pq.push(pii(cost, graph[cur][i].first));
            }
        }
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr); cout.tie(nullptr);

    cin &gt;&gt; m &gt;&gt; n;
    graph.resize(n + 1);

    for (int i = 0; i &lt; m; i++)
    {
        int a, b, c;
        cin &gt;&gt; a &gt;&gt; b &gt;&gt; c;
        graph[a].push_back(pii(b, c));
    }

    // 0 ~ n까지 각 위치에서 +1 위치로 간선 연결 (길이는 1)
    for (int i = 0; i &lt;= n; i++)
        graph[i].push_back(pii(i + 1, 1));

    fill(dp, dp + n + 1, INF);
    dijkstra(0);

    // 0 ~ n이 현재 위치일 때 -&gt; 현재 위치까지의 최소 거리 + (목적지 - 현재 위치)를 전부 비교하여 가장 작은 값 찾기
    int minDist = INF;
    for (int i = 0; i &lt;= n; i++)
    {
        int curDist = dp[i] + (n - i);    
        if (curDist &lt; minDist)
            minDist = curDist;
    }
    cout &lt;&lt; minDist;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] 다익스트라(Dijkstra) 알고리즘]]></title>
            <link>https://velog.io/@doorbals_512/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%8B%A4%EC%9D%B5%EC%8A%A4%ED%8A%B8%EB%9D%BCDijkstra-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</link>
            <guid>https://velog.io/@doorbals_512/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%8B%A4%EC%9D%B5%EC%8A%A4%ED%8A%B8%EB%9D%BCDijkstra-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</guid>
            <pubDate>Fri, 03 Mar 2023 11:36:40 GMT</pubDate>
            <description><![CDATA[<h2 id="1-다익스트라-알고리즘">1. 다익스트라 알고리즘</h2>
<ul>
<li>특정 노드에서 출발하여 다른 모든 노드로 가는 <strong>최단 경로</strong>를 계산</li>
<li><strong>음의 간선이 없을 때</strong> 정상적으로 동작<ul>
<li>현실 세계의 도로(간선)는 음의 간선으로 표현되지 않는다.</li>
</ul>
</li>
<li><strong>그리디 알고리즘</strong>으로 분류된다.<ul>
<li>매 상황에서 방문하지 않은 <strong>가장 비용이 적은 노드를 선택</strong>해 임의의 과정을 반복</li>
<li>단계를 거치며 <strong>한 번 처리된 노드의 최단 거리는 고정</strong>되어 더 이상 바뀌지 않는다.<ul>
<li>한 단계당 하나의 노드에 대한 최단 거리를 확실히 찾는 것으로 이해 가능</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<h2 id="2-동작-과정">2. 동작 과정</h2>
<p><strong>1)</strong> 출발 노드를 설정한다.
<strong>2)</strong> 최단 거리 테이블을 초기화한다. (다른 노드로 가는 거리를 무한으로 설정. 자기 자신은 0)
<strong>3)</strong> 방문하지 않은 노드 중에서 최단 거리가 가장 짧은 노드를 선택한다.
<strong>4)</strong> 해당 노드를 거쳐 다른 노드로 가는 비용을 계산하여 최단 거리 테이블을 갱신한다.
<strong>5)</strong> 위 과정에서 3)과 4)를 반복한다.
<strong>6)</strong> 알고리즘이 끝나고 나면 최단 거리 테이블에 각 노드까지의 최단 거리 정보가 저장된다.</p>
<hr>
<h2 id="3-동작-예시">3. 동작 예시</h2>
<p><img src="https://velog.velcdn.com/images/doorbals_512/post/0d935528-816a-48e9-bcdf-a88aee55cf53/image.png" alt=""></p>
<p><strong>1)</strong> 최단 거리 테이블에서 출발 노드 자신에 대해서는 0, 다른 노드들에 대해서는 무한으로 초기화</p>
<p><img src="https://velog.velcdn.com/images/doorbals_512/post/2dba276b-7e76-4ac9-8bdc-f0f8c9bf7a96/image.png" alt=""></p>
<p><strong>2)</strong> 방문하지 않은 노드 중에서 최단 거리가 가장 짧은 노드는 1번이므로 1번 노드를 거쳐갈 때 인접 노드들의 최단 거리 테이블을 갱신한다. (기존 값보다 작은 값일 때)</p>
<ul>
<li>2번 노드 : 1번 노드까지 최단 거리 0 + 1번 노드 ~ 2번 노드까지의 거리 2 = 2 (&lt; 무한)</li>
<li>3번 노드 : 1번 노드까지 최단 거리 0 + 1번 노드 ~ 3번 노드까지의 거리 5 = 5 (&lt; 무한)</li>
<li>4번 노드 : 1번 노드까지 최단 거리 0 + 1번 노드 ~ 4번 노드까지의 거리 1 = 1 (&lt; 무한)</li>
</ul>
<p><img src="https://velog.velcdn.com/images/doorbals_512/post/367ecaa0-1af3-4ed8-b063-c8dbcfc73f8f/image.png" alt=""></p>
<p><strong>3)</strong> 방문하지 않은 노드 중에서 최단 거리가 가장 짧은 노드는 4번이므로 4번 노드를 거쳐갈 때 인접 노드들의 최단 거리 테이블을 갱신한다. (기존 값보다 작은 값일 때)</p>
<ul>
<li>3번 노드 : 4번 노드까지 최단 거리 1 + 4번 노드 ~ 3번 노드까지의 거리 3 = 4 (&lt; 5)</li>
<li>5번 노드 : 4번 노드까지 최단 거리 1 + 4번 노드 ~ 5번 노드까지의 거리 1 = 2 (&lt; 무한)</li>
</ul>
<p><img src="https://velog.velcdn.com/images/doorbals_512/post/9a06efd1-1358-48fe-bc09-60dabcc18700/image.png" alt=""></p>
<p><strong>4)</strong> 방문하지 않은 노드 중에서 최단 거리가 가장 짧은 노드는 2번이므로 2번 노드를 거쳐갈 때 인접 노드들의 최단 거리 테이블을 갱신한다. (기존 값보다 작은 값일 때)</p>
<ul>
<li>3번 노드 : 2번 노드까지 최단 거리 2 + 2번 노드 ~ 3번 노드까지의 거리 3 = 5 (&gt; 4)</li>
<li>4번 노드 : 2번 노드까지 최단 거리 2 + 2번 노드 ~ 4번 노드까지의 거리 2 = 4 (&gt; 1)
=&gt; 4번 노드는 <strong>이미 방문 처리 된 노드여서 값이 확정</strong>되었으므로 이 같은 경우에는 더이상 확인하지 않아도 된다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/doorbals_512/post/7f707fd9-fc9b-45c8-9a26-be7b9ffbd6d4/image.png" alt=""></p>
<p><strong>5)</strong> 방문하지 않은 노드 중에서 최단 거리가 가장 짧은 노드는 5번이므로 5번 노드를 거쳐갈 때 인접 노드들의 최단 거리 테이블을 갱신한다. (기존 값보다 작은 값일 때)</p>
<ul>
<li>3번 노드 : 5번 노드까지 최단 거리 2 + 5번 노드 ~ 3번 노드까지의 거리 1 = 3 (&lt; 4)</li>
<li>6번 노드 : 5번 노드까지 최단 거리 2 + 5번 노드 ~ 6번 노드까지의 거리 2 = 4 (&lt; 무한)</li>
</ul>
<p><img src="https://velog.velcdn.com/images/doorbals_512/post/c3be3e37-743b-48f2-91d4-f7d2e4889d89/image.png" alt=""></p>
<p><strong>6)</strong> 방문하지 않은 노드 중에서 최단 거리가 가장 짧은 노드는 3번이므로 3번 노드를 거쳐갈 때 인접 노드들의 최단 거리 테이블을 갱신한다. (기존 값보다 작은 값일 때)</p>
<ul>
<li>2번 노드 : 3번 노드까지 최단 거리 3 + 3번 노드 ~ 2번 노드까지의 거리 3 = 6 (&gt; 2)</li>
<li>6번 노드 : 3번 노드까지 최단 거리 3 + 3번 노드 ~ 6번 노드까지의 거리 5 = 8 (&gt; 4)</li>
</ul>
<p><img src="https://velog.velcdn.com/images/doorbals_512/post/a0f5c932-6f5b-4602-a062-94de8651f115/image.png" alt=""></p>
<p><strong>7)</strong> 마지막까지 남은 노드 6에 대해서는 처리하지 않아도 된다.</p>
<hr>
<h2 id="4-우선순위-큐를-이용한-동작-과정">4. 우선순위 큐를 이용한 동작 과정</h2>
<p><strong>1)</strong> 출발 노드를 설정한다.
<strong>2)</strong> 최단 거리 테이블을 초기화한다. (다른 노드로 가는 거리를 무한대로 설정. 자기 자신은 0)
<strong>3)</strong> 우선순위 큐를 생성하고 출발 노드를 우선순위 큐에 넣는다. (거리 기준 오름차순 정렬)
<strong>4)</strong> 우선순위 큐의 가장 위에 있는 노드를 꺼내고, 해당 노드가 방문하지 않은 노드라면 그 노드의 인접 노드들까지의 거리를 기존 최단 거리 테이블과 비교한다.
<strong>5)</strong> 기존 최단 거리보다 짧으면 갱신한 뒤 해당 노드들을 우선순위 큐에 넣는다.
<strong>5)</strong> 위 과정에서 4)와 5)를 반복한다.
<strong>6)</strong> 알고리즘이 끝나고 나면 최단 거리 테이블에 각 노드까지의 최단 거리 정보가 저장된다.</p>
<hr>
<h2 id="5-코드-예시">5. 코드 예시</h2>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;algorithm&gt;
#include &lt;queue&gt;
using namespace std;
typedef pair&lt;int, int&gt; pii;

int n, m, start;            // 노드의 개수, 간선의 개수, 시작 노드 번호
vector&lt;vector&lt;pii&gt;&gt; graph;    // 그래프 데이터 저장
int d[100001];                // 최단 거리 테이블
const long long INF = 1e9;    // 무한을 의미하는 값

void dijkstra(int start)
{
    priority_queue&lt;pii, vector&lt;pii&gt;, greater&lt;pii&gt;&gt; pq;    // priority_queue는 최대힙으로 구현되어 있으므로, 최소힙으로 작동하기 위해 greater 추가
    pq.push(pii(0, start));
    d[start] = 0;

    while (!pq.empty())
    {
        // 가장 최단거리가 짧은 노드에 대한 정보 꺼내기
        int dist = pq.top().first;    // 현재 노드까지의 비용
        int cur = pq.top().second;    // 현재 노드 번호
        pq.pop();
        // 현재 노드가 이미 처리된 적이 있는 노드라면 무시
        if (d[cur] &lt; dist) continue;
        // 현재 노드와 연결된 다른 인접 노드들을 확인
        for (int i = 0; i &lt; graph[cur].size(); i++)
        {
            pii curEdge = graph[cur][i];
            int cost = dist + curEdge.second;
            // 현재 노드를 거쳐서 다른 노드로 이동하는 거리가 더 짧은 경우
            if (cost &lt; d[curEdge.first])
            {
                d[curEdge.first] = cost;
                pq.push(pii(cost, curEdge.first));
            }
        }
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr); cout.tie(nullptr);

    cin &gt;&gt; n &gt;&gt; m &gt;&gt; start;

    graph.resize(n + 1);
    // 간선 정보 입력 받기
    for (int i = 0; i &lt; m; i++)
    {
        int a, b, c;
        cin &gt;&gt; a &gt;&gt; b &gt;&gt; c;
        graph[a].push_back(pii(b, c));    // a 노드에서 b 노드로 가는 비용이 c라는 의미
    }

    // 최단거리 테이블을 모두 무한으로 초기화
    fill(d, d + 100001, INF);

    // 다익스트라 알고리즘 수행
    dijkstra(start);

    // 모든 노드로 가기 위한 최단 거리를 출력
    for (int i = 1; i &lt;= n + 1; i++)
    {
        if (d[i] == INF)
            cout &lt;&lt; &quot;INFINITY&quot; &lt;&lt; endl;
        else
            cout &lt;&lt; d[i] &lt;&lt; endl;
    }
}</code></pre>
<h2 id="6-시간복잡도">6. 시간복잡도</h2>
<ul>
<li>힙 자료구조 이용하는 다익스트라 알고리즘의 시간 복잡도는 <strong>O(ElogV)</strong></li>
<li>노드를 하나씩 꺼내 검사하는 반복문은 노드의 개수 V 이상 횟수로는 처리되지 않는다.<ul>
<li>결과적으로 현재 우선순위 큐에서 꺼낸 노드와 연결된 다른 노드들을 확인하는 총 횟수는 최대 간선 개수(E)만큼 연산이 수행될 수 있다.</li>
</ul>
</li>
<li>직관적으로 전체 과정은 E개의 원소를 우선순위 큐에 넣었다가 모두 빼내는 연산과 유사하다.<ul>
<li>시간 복잡도를 O(ElogE)로 판단할 수 있다.</li>
<li>중복 간선을 포함하지 않는 경우 이를 O(ElogV)로 정리할 수 있다.<ul>
<li>O(ElogE) -&gt; O(ElogV^2) -&gt; O(2ElogV) -&gt; O(ElogV)</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<p><strong>👁️‍🗨️ 참고</strong>
<a href="https://www.youtube.com/watch?v=acqm9mM1P6o&amp;t=514s">https://www.youtube.com/watch?v=acqm9mM1P6o&amp;t=514s</a> (동빈나 채널)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[UNSEEN] 스마일게이트 UNSEEN 지원 후기 ]]></title>
            <link>https://velog.io/@doorbals_512/UNSEEN-%EC%8A%A4%EB%A7%88%EC%9D%BC%EA%B2%8C%EC%9D%B4%ED%8A%B8-UNSEEN-%EC%A7%80%EC%9B%90-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@doorbals_512/UNSEEN-%EC%8A%A4%EB%A7%88%EC%9D%BC%EA%B2%8C%EC%9D%B4%ED%8A%B8-UNSEEN-%EC%A7%80%EC%9B%90-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Fri, 03 Mar 2023 07:39:37 GMT</pubDate>
            <description><![CDATA[<h2 id="unseen">UNSEEN?</h2>
<p><strong>스마일게이트 x 에픽게임즈</strong> 에서 진행하는 언리얼 프로그래머 양성 교육으로, 이번이 1기 첫 모집이었다. 약 4개월의 시간 동안 제공되는 언리얼 엔진 교육을 들으면서, 자기주도 프로젝트를 진행하여 결과물을 구현하는 것이 교육 목표이다. 최근까지 유니티 교육을 받아 왔지만, 앞으로 내 목표를 위해서는 언리얼 엔진을 배워야 하지 않을까 라고 생각하던 중에 해당 교육을 알게 되어 지원했다.</p>
<hr>
<h2 id="선발-과정">선발 과정</h2>
<h3 id="1-서류">1) 서류</h3>
<p>서류 과정은 아래와 같이 필수 제출 항목과 선택 제출 항목으로 나눠져 있었다.</p>
<ul>
<li><strong>필수 제출</strong><ul>
<li>지원 동기 및 목표</li>
<li>프로젝트 계획서</li>
</ul>
</li>
<li><strong>선택 제출</strong><ul>
<li>자기소개 및 개발 경험</li>
<li>기술 포트폴리오</li>
</ul>
</li>
</ul>
<p>이 프로그램에 대한 열정을 보이기 위해서는 모든 항목을 제출하는 것이 당연하다고 생각하여 필수, 선택 여부에 상관없이 전부 제출했다. 지원 동기 및 목표는 지금까지 유니티 엔진을 사용하면서 느낀 언리얼 엔진의 필요성을 중점으로 작성했다. </p>
<p>그리고 앞으로 4개월 동안 진행할 프로젝트 계획서를 준비했는데, 지금까지 대부분 모작 프로젝트만 진행했던 것과 달리 이번에는 시놉시스부터 기능까지 내가 만들어 보고 싶은 게임을 처음으로 기획해봤다. 만약에 선정이 안 되더라도 혼자서 이 계획서로 프로젝트를 진행해 봐야겠다고 생각할 정도로 열심히 준비했다. </p>
<p>선택 제출 항목들은 이전에 메타버스 아카데미에서 진행했던 유니티 프로젝트들을 중심으로 각 프로젝트를 진행하면서 사용한 기술, 느낀 점 등의 내용을 작성하여 제출했다. </p>
<h3 id="2-테스트">2) 테스트</h3>
<p>테스트 과정은 미리 알려주신 주제 내에서 출제되는 객관식·주관식 혼합형 온라인 테스트였다. C++, 자료구조, 게임 알고리즘, 게임 수학 등과 같은 주제로 출제되었는데, 주제가 미리 주어졌었기 때문에 관련 내용들을 정리하고 공부해 두어서 많이 어렵지는 않았다. 다만 내가 수학에 약해서 게임 수학 관련 내용들이 좀 어렵게 느껴졌던 것 같다.. 수학 공부 열심히 해야지..</p>
<h3 id="3-면접">3) 면접</h3>
<p>면접 과정은 판교에 있는 스마일게이트 건물에서 대면 면접으로 진행되었는데, 이런 면접은 처음 해보는 거라 그 전날부터 엄청 떨렸었다. 판교까지 가는 내내 미리 써둔 자기소개만 외우면서 갔다 ㅎㅎ.. </p>
<p>면접은 약 40분 동안 3 : 1 (면접관님들 : 나) 로, 간단한 자기소개 + 프로젝트 계획서 설명 이후 면접관님들의 질문 순서로 진행되었다. 서류 단계 때 제출한 포트폴리오에 대한 질문이 많이 나올 것 같아서 포트폴리오 위주로 보고 갔었는데, 포트폴리오 관련 질문은 하나도 안 나왔다.. 거의 CS 지식이나 알고리즘, 게임 수학 위주로 질문하셨다. </p>
<p>대답 못 한 질문이 꽤 있어서 나오면서 아쉬운 마음이 들었었다. 여담으로 시설이 정말 좋아서 나중에 취업 준비 열심히 해서 꼭 이런 곳에서 일하고 싶다는 생각이 들기도 했다! </p>
<hr>
<h2 id="결과">결과</h2>
<p><img src="https://velog.velcdn.com/images/doorbals_512/post/54b56128-a7fd-4c3a-bbd5-7a6c07e9b4e0/image.png" alt=""></p>
<p>면접을 본 뒤 1주 후인 어제 결과가 발표되었고, 최종 합격하게 되었다! 인턴 채용도 아니고 남들이 보기엔 별 거 아닐 수도 있겠지만, 면접 본 이후 결과가 나오기만을 기다리고 있었어서 결과 확인 후에 너무 기뻤다😢 이 프로그램이 내가 한 단계 더 성장할 수 있는 계기가 되었으면 좋겠다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 11054번 : 가장 긴 바이토닉 부분 수열]]></title>
            <link>https://velog.io/@doorbals_512/%EB%B0%B1%EC%A4%80-11054%EB%B2%88-%EA%B0%80%EC%9E%A5-%EA%B8%B4-%EB%B0%94%EC%9D%B4%ED%86%A0%EB%8B%89-%EB%B6%80%EB%B6%84-%EC%88%98%EC%97%B4</link>
            <guid>https://velog.io/@doorbals_512/%EB%B0%B1%EC%A4%80-11054%EB%B2%88-%EA%B0%80%EC%9E%A5-%EA%B8%B4-%EB%B0%94%EC%9D%B4%ED%86%A0%EB%8B%89-%EB%B6%80%EB%B6%84-%EC%88%98%EC%97%B4</guid>
            <pubDate>Thu, 02 Mar 2023 17:36:53 GMT</pubDate>
            <description><![CDATA[<h2 id="🔗-문제-링크">🔗 문제 링크</h2>
<p><a href="https://www.acmicpc.net/problem/11054">https://www.acmicpc.net/problem/11054</a></p>
<hr>
<h2 id="✍️-문제-풀이">✍️ 문제 풀이</h2>
<p>해당 문제는 <strong>다이나믹 프로그래밍</strong> 문제로, <strong>LIS</strong> 문제를 응용하여 푸는 문제이다.
최장 바이토닉 부분 수열은 ** 특정 인덱스에서의 최장 증가 수열과 최장 감소 수열을 합친 것의 최대값**이라고 볼 수 있다.
ex. 수열 {50 10 20 30 25 20}의 최장 바이토닉 부분 수열은</p>
<ul>
<li>인덱스 4에서의 최장 증가 수열인 {10 20 30}</li>
<li>인덱스 4에서의 최장 감소 수열인 {30 25 20}</li>
<li>위 둘을 합친 {10 20 30 25 20} 이 최장 바이토닉 부분 수열이 된다.</li>
</ul>
<p><strong>1)</strong> <code>nums</code>에 수열을 입력받아 저장하고, <code>nums2</code>에 입력 받은 수열을 역순으로 저장한다.</p>
<p><strong>2)</strong> 배열 <code>dp1</code>, <code>dp2</code>를 선언하고, 각각을 차례대로 갱신한다.</p>
<ul>
<li><strong>dp1[i] : i 번째 수까지 있을 때, 수열의 부분 수열 중 최장 증가 수열의 길이</strong></li>
<li><strong>dp2[i] : i 번째 수까지 있을 때, 역순 수열의 부분 수열 중 최장 증가 수열의 길이 
== 수열의 부분 수열 중 최장 감소 수열의 길이</strong></li>
</ul>
<p><strong>3)</strong> i가 0 ~ n일 때 </p>
<ul>
<li><p>(dp1[i]와 dp2[n - i + 1]의 합 - 1)을 구해 <code>maxValue</code>와 비교한다.</p>
<ul>
<li><strong>-1을 해주는 이유 : 가운데 수가 중복되어 더해지기 때문이다.</strong>
ex. 인덱스 i에서의 최장 증가 부분 수열이 {10 20 30} 이고, 최장 감소 부분 수열이 {30 25 20} 일 때 최장 바이토닉 부분 수열은 {10 20 30 25 20}인데, 이 때 30이 중복되기 때문에 1을 빼준다.</li>
</ul>
</li>
<li><p>만약 위 수가 <code>maxValue</code>보다 크다면 <code>maxValue</code>를 위 수로 갱신한다.</p>
<p><img src="https://velog.velcdn.com/images/doorbals_512/post/1b44fae9-be98-44c4-8dfa-1d00c90c0fcc/image.png" alt=""></p>
</li>
</ul>
<p><strong>4)</strong> 최종적으로 <code>maxValue</code>에 최장 바이토닉 부분 수열의 길이가 저장된다.</p>
<hr>
<h2 id="🖥️-풀이-코드">🖥️ 풀이 코드</h2>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;algorithm&gt;
using namespace std;

int nums[1001];
int nums2[1001];
int dp1[1001];        
int dp2[1001];

int solution(int n)
{
    for (int i = 1; i &lt;= n; i++)
    {
        for (int j = 1; j &lt; i; j++)
        {
            if (nums[i] &gt; nums[j] &amp;&amp; dp1[j] + 1 &gt; dp1[i])
                dp1[i] = dp1[j] + 1;

            if (nums2[i] &gt; nums2[j] &amp;&amp; dp2[j] + 1 &gt; dp2[i])
                dp2[i] = dp2[j] + 1;
        }
    }

    int maxValue = 0;
    for (int i = 0; i &lt;= n; i++)
    {
        int sum = dp1[i] + dp2[n - i + 1] - 1;
        if (maxValue &lt; sum)
            maxValue = sum;
    }
    return maxValue;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr); cout.tie(nullptr);

    int n;
    cin &gt;&gt; n;

    for (int i = 1; i &lt;= n; i++)
    {
        cin &gt;&gt; nums[i];
        nums2[n - i + 1] = nums[i];
        dp1[i] = 1;
        dp2[i] = 1;
    }

    cout &lt;&lt; solution(n);
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 2565번 : 전깃줄]]></title>
            <link>https://velog.io/@doorbals_512/%EB%B0%B1%EC%A4%80-2565%EB%B2%88-%EC%A0%84%EA%B9%83%EC%A4%84</link>
            <guid>https://velog.io/@doorbals_512/%EB%B0%B1%EC%A4%80-2565%EB%B2%88-%EC%A0%84%EA%B9%83%EC%A4%84</guid>
            <pubDate>Thu, 02 Mar 2023 17:17:45 GMT</pubDate>
            <description><![CDATA[<h2 id="🔗-문제-링크">🔗 문제 링크</h2>
<p><a href="https://www.acmicpc.net/problem/2565">https://www.acmicpc.net/problem/2565</a></p>
<hr>
<h2 id="✍️-문제-풀이">✍️ 문제 풀이</h2>
<p>해당 문제는 <strong>다이나믹 프로그래밍</strong> 문제로, <strong>LIS</strong> 문제를 응용하여 푸는 문제이다.</p>
<p><strong>1)</strong> <code>n</code>에 전깃줄의 개수를 입력받아 저장한다.</p>
<p><strong>2)</strong> <code>edges</code> 벡터를 선언하고, <code>n</code>개 만큼의 전깃줄에 대해 pair(A 전봇대 번호, B 전봇대 번호)를 입력받아 저장한다. 그리고 A 전봇대 번호를 기준으로 <code>edges</code>를 오름차순 정렬한다.</p>
<p><strong>3)</strong> A 전봇대 번호의 오름차순으로 A 전봇대와 연결된 B 전봇대의 번호들을 정렬하면 하나의 수열이 만들어진다. 겹치는 전깃줄이 없으려면 (모든 A 전봇대 번호에 연결된 B 전봇대 번호)는 (더 큰 A 전봇대 번호와 연결된 B 전봇대 번호)보다 작아야 한다.  </p>
<ul>
<li>ex. (A 전봇대 1 -  B 전봇대 5) 와 (A 전봇대 2 - B 전봇대 4) 가 있으면 서로 겹치게 됨.</li>
</ul>
<p><strong>4)</strong> 따라서 만들어진 수열에서 최장 증가 수열을 구하면 
-&gt; 가장 많은 전깃줄 개수를 유지함과 동시에 전깃줄이 겹치지 않는 경우이다. </p>
<p><strong>5)</strong> 최장 증가 수열(LIS) 구하기</p>
<ul>
<li><strong>dp[i] : 수열이 i 번째 인덱스까지 있을 때, i 번째 수를 포함하는 최장 증가 수열의 길이</strong></li>
<li>i 번째 인덱스의 수와 j(0 ~ i-1) 번째 수를 차례대로 비교하며 dp를 갱신한다.<ul>
<li>만약 (i 번째 수 &gt; j 번째 수) and (dp[j] + 1 &gt; dp[i]) 라면<ul>
<li>dp[i]를 dp[j] + 1로 갱신한다.</li>
<li><code>maxCount</code> &lt; dp[i]면 <code>maxCount</code>를 dp[i]로 갱신한다. 
(maxCount = 현재까지 최장 증가 수열 길이)</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><strong>6)</strong> 전체 수열 길이 <code>n</code>에서 <code>maxCount</code>를 빼면 필요 없는 전깃줄 == 제거해야하는 전깃줄의 개수가 나온다.</p>
<hr>
<h2 id="🖥️-풀이-코드">🖥️ 풀이 코드</h2>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;algorithm&gt;
using namespace std;
typedef pair&lt;int, int&gt; pii;

int n;
vector&lt;pii&gt; edges;    // 전선의 시작 번호, 끝 번호 저장
int dp[101];

int solution()
{
    int maxCount = 0;
    for (int i = 1; i &lt;= n; i++)
    {
        for (int j = 1; j &lt; i; j++)
        {
            if (edges[j].second &lt; edges[i].second &amp;&amp; dp[j] + 1 &gt; dp[i])
            {
                dp[i] = dp[j] + 1;

                if (maxCount &lt; dp[i])
                    maxCount = dp[i];
            }
        }
    }
    return n - maxCount;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr); cout.tie(nullptr);

    cin &gt;&gt; n;
    edges.push_back(pii(0, 0));
    for (int i = 1; i &lt;= n; i++)
    {
        int a, b;
        cin &gt;&gt; a &gt;&gt; b;
        edges.push_back(pii(a, b));
        dp[i] = 1;
    }
    sort(edges.begin(), edges.end());
    cout &lt;&lt; solution();
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 9252번 : LCS2]]></title>
            <link>https://velog.io/@doorbals_512/%EB%B0%B1%EC%A4%80-9252%EB%B2%88-LCS2</link>
            <guid>https://velog.io/@doorbals_512/%EB%B0%B1%EC%A4%80-9252%EB%B2%88-LCS2</guid>
            <pubDate>Thu, 02 Mar 2023 16:52:07 GMT</pubDate>
            <description><![CDATA[<h2 id="🔗-문제-링크">🔗 문제 링크</h2>
<p><a href="https://www.acmicpc.net/problem/9252">https://www.acmicpc.net/problem/9252</a></p>
<hr>
<h2 id="✍️-문제-풀이">✍️ 문제 풀이</h2>
<p>해당 문제는 <strong>다이나믹 프로그래밍</strong> 문제로, 1 ~ n 인덱스까지의 A 문자열이 있을 때,  1 ~ n 인덱스까지의 B 문자열과의 최장 공통 수열을 2차원 dp 배열에 차례대로 갱신하는 <strong>Bottom-Up</strong> 방식으로 풀었다.</p>
<ul>
<li>이 문제는 기존 LCS 문제와 같으나, 개수 뿐만 아니라 해당 수열을 직접 출력까지 해야한다. 
따라서 LCS의 길이를 구하는 부분은 생략하고, 수열을 출력하는 부분만 알아보겠다.
<a href="https://velog.io/@doorbals_512/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-LCS-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EC%9D%B8%EB%8D%B0-DP%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C">(9251번 : LCS 링크)</a></li>
</ul>
<p><strong>1)</strong> A 문자열의 길이가 n이고, B 문자열의 길이가 m이라고 할 때, dp[n][m]에는 A 문자열과 B 문자열 사이의 최장 증가 수열의 길이가 저장된다. 따라서 이 dp[n][m]까지 오는 경로를 역추적하면 최장 증가 수열이 어떤 문자로 구성되어있는지 알 수 있다.</p>
<p><strong>2)</strong> 변수 <code>i</code>, <code>j</code>를 선언하고, 각각 n과 m으로 초기화한다. 또한 최장 증가 수열을 저장할 문자열 변수 <code>answer</code>를 선언한다.</p>
<p><strong>3)</strong> i와 j가 모두 0이 될 때까지 아래 과정을 반복한다.</p>
<ul>
<li>만약 str1[i] == str2[j]일 때(현재 순서 A 문자열의 글자와 B 문자열의 글자가 같을 때)<ul>
<li><code>i</code>와 <code>j</code>를 1씩 감소시키고, <code>answer</code>에 str1[i](현재 순서 글자)를 추가한다.</li>
<li>두 문자열의 현재 순서 글자가 같은 글자일 때만 수열 길이가 증가하기 때문이다.</li>
</ul>
</li>
<li>위 경우가 아니라면<ul>
<li>만약 dp[i][j] == dp[i - 1][j] 라면 
(A 문자열의 길이가 B 문자열보다 1 짧은 경우의 최장 증가 수열의 길이를 가져온 경우)<ul>
<li><code>i</code>를 1 감소시킨다.</li>
</ul>
</li>
<li>만약 dp[i][j] == dp[i][j - 1] 라면 
(B 문자열의 길이가 A 문자열보다 1 짧은 경우의 최장 증가 수열의 길이를 가져온 경우)<ul>
<li><code>j</code>를 1 감소시킨다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><strong>4)</strong> <code>answer</code>에 최장 증가 수열이 뒤집힌 상태로 저장되어있으므로, 이를 반대로 출력한다.</p>
<hr>
<h2 id="🖥️-풀이-코드">🖥️ 풀이 코드</h2>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;algorithm&gt;
#include &lt;string&gt;
using namespace std;

int n, m;
int dp[1001][1001];
string str1, str2, answer;

void solution()
{
    int maxCount = 0;
    for (int i = 1; i &lt;= n; i++)
    {
        for (int j = 1; j &lt;= m; j++)
        {
            if (str1[i - 1] == str2[j - 1])
            {
                dp[i][j] = dp[i - 1][j - 1] + 1;
                if (maxCount &lt; dp[i][j])
                {
                    maxCount = dp[i][j];
                }
            }
            else
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
        }
    }
    cout &lt;&lt; dp[n][m] &lt;&lt; endl;

    int i = n, j = m;
    while (i != 0 &amp;&amp; j != 0)
    {
        if (str1[i - 1] == str2[j - 1])
        {
            answer += str1[i - 1];
            i--;
            j--;
        }
        else
        {
            if (dp[i][j] == dp[i - 1][j])
                i--;
            else if (dp[i][j] == dp[i][j - 1])
                j--;
        }
    }

    for(int i = answer.length() - 1; i &gt;= 0; i--)
        cout &lt;&lt; answer[i];
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr); cout.tie(nullptr);

    cin &gt;&gt; str1;
    cin &gt;&gt; str2;
    n = str1.size();
    m = str2.size();
    solution();
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 11052번 : 카드 구매하기]]></title>
            <link>https://velog.io/@doorbals_512/%EB%B0%B1%EC%A4%80-11052%EB%B2%88-%EC%B9%B4%EB%93%9C-%EA%B5%AC%EB%A7%A4%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@doorbals_512/%EB%B0%B1%EC%A4%80-11052%EB%B2%88-%EC%B9%B4%EB%93%9C-%EA%B5%AC%EB%A7%A4%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 28 Feb 2023 17:24:54 GMT</pubDate>
            <description><![CDATA[<h2 id="🔗-문제-링크">🔗 문제 링크</h2>
<p><a href="https://www.acmicpc.net/problem/11052">https://www.acmicpc.net/problem/11052</a></p>
<hr>
<h2 id="✍️-문제-풀이">✍️ 문제 풀이</h2>
<p>해당 문제는 <strong>다이나믹 프로그래밍</strong> 문제로, 1 ~ n 종류의 카드팩까지 있을 때 1 ~ n 개의 카드를 사는 최대 금액을 2차원 dp 배열에 차례대로 갱신하는 <strong>Bottom-Up</strong> 방식으로 풀었다.</p>
<ul>
<li>이 문제는 평범한 배낭 문제와 유사하나, 배낭 문제는 현재 순서 물건이 하나밖에 존재하지 않는 반면, 해당 문제는 같은 물건(== 카드팩)을 여러 개 사용 가능하다.</li>
<li>따라서 현재 순서 물건을 넣는 경우에 현재 물건을 포함하는 경우의 dp값을 참조한다. 
(배낭 문제는 직전 물건까지만 있을 때의 dp를 참조)</li>
</ul>
<p><strong>1)</strong> <code>n</code>에 구매하고자 하는 카드의 개수를 입력받아 저장한다.</p>
<p><strong>2)</strong> 2차원 배열 <code>dp</code>를 선언하고, 각 dp를 차례대로 갱신한다.</p>
<ul>
<li><strong>dp[i][j] : i 번째 카드팩까지 있을 때, j개의 카드를 사는 최대 금액</strong></li>
<li>문제 조건에 의해 n개보다 많은 개수의 카드를 살 수 없다. 따라서 현재 순서의 카드팩으로 j개의 카드를 사는 경우는 두 가지로 나뉜다.<ul>
<li>현재 순서 카드팩에 사고자 하는 카드 개수보다 많은 카드가 들어있을 때 <strong>(i &gt; j)</strong>
: 이 때는 현재 순서(i) 카드팩으로 j개의 카드를 살 수 없으므로 직전 순서(i-1) 카드팩까지 있을 때 j개의 카드를 사는 최대 금액과 같다. </li>
<li>위 경우가 아닐 때는 아래 두 경우 중 큰 값을 dp[i][j]로 갱신한다.<ul>
<li>현재 순서 카드팩을 무조건 한 개 이상 넣는 경우 
: 현재 순서 카드팩까지 있을 때 (j-현재 순서 카드팩 개수)개 카드 사는 최대 금액 + 현재 순서 카드팩의 금액</li>
<li>현재 순서 카드팩을 사지 않는 경우 
: 직전 순서 카드팩까지 있을 때 j개의 카드 사는 최대 금액</li>
</ul>
</li>
<li>위 과정을 <strong>점화식</strong>으로 나타내면</li>
<li>각 i와 j에 대해 (i : 현재 카드 순서 (1 ~ n) / j : 사고자 하는 카드 개수 (1 ~ n))</li>
<li><strong>if(i &gt; j) dp[i][j] = dp[i - 1][j]</strong></li>
<li><strong>else dp[i][j] = max(dp[i][j - i] + costs[i], dp[i - 1][j])</strong></li>
</ul>
</li>
</ul>
<p><strong>3)</strong> dp[n][n]에 카드 종류가 n가지 있을 때, n개의 카드를 사는 최대 금액이 저장되므로 이를 출력한다.</p>
<hr>
<h2 id="🖥️-풀이-코드">🖥️ 풀이 코드</h2>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;algorithm&gt;
#include &lt;cmath&gt;
using namespace std;

int n;
vector&lt;int&gt; costs;
int dp[1001][1001];        // dp[i][j] : i 번째 카드팩까지 있을 때 j개의 카드를 사는 최대 금액

int solution() 
{
    for (int i = 1; i &lt;= n; i++)
    {
        for (int j = 1; j &lt;= n; j++)
        {
            if (i &gt; j)        
                dp[i][j] = dp[i - 1][j];
            else            
                dp[i][j] = max(dp[i][j - i] + costs[i], dp[i - 1][j]);                 
        }
    }
    return dp[n][n];
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr); cout.tie(nullptr);

    cin &gt;&gt; n;
    costs.push_back(0);
    for (int i = 0; i &lt; n; i++)
    {
        int num;
        cin &gt;&gt; num;
        costs.push_back(num);
    }
    cout &lt;&lt; solution();
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 10844번 : 쉬운 계단 수]]></title>
            <link>https://velog.io/@doorbals_512/%EB%B0%B1%EC%A4%80-10844%EB%B2%88-%EC%89%AC%EC%9A%B4-%EA%B3%84%EB%8B%A8</link>
            <guid>https://velog.io/@doorbals_512/%EB%B0%B1%EC%A4%80-10844%EB%B2%88-%EC%89%AC%EC%9A%B4-%EA%B3%84%EB%8B%A8</guid>
            <pubDate>Mon, 27 Feb 2023 16:26:15 GMT</pubDate>
            <description><![CDATA[<h2 id="🔗-문제-링크">🔗 문제 링크</h2>
<p><a href="https://www.acmicpc.net/problem/10844">https://www.acmicpc.net/problem/10844</a></p>
<hr>
<h2 id="✍️-문제-풀이">✍️ 문제 풀이</h2>
<p>해당 문제는 <strong>다이나믹 프로그래밍</strong> 문제로, 각 자리 계단 수 중에 맨 끝이 0 ~ 9로 끝나는 수의 개수를 2차원 dp 배열에 차례대로 갱신하는 <strong>Bottom-Up</strong> 방식으로 풀었다.</p>
<p><strong>1)</strong> <code>n</code>에 계단 수의 개수를 구하고자 하는 자리 수를 입력받아 저장한다.</p>
<p><strong>2)</strong> 2차원 배열 <code>dp</code>를 선언하고, dp[1][1 ~ 9]를 모두 1로 초기화 한다.</p>
<ul>
<li><strong>dp[i][j] : i자리 수인 계단 수 중에 맨 끝이 j인 수의 개수</strong></li>
<li>1자리 계단 수는 1<del>9가 존재. 따라서 1자리 계단 수 중 1</del>9로 끝나는 수의 개수는 각 1개</li>
</ul>
<p><strong>3)</strong> i가 2 ~ n일 때까지 계단 수의 개수를 전부 갱신한다.</p>
<ul>
<li>현재 자리 수의 계단 수는 이전 자리 계단 수 끝에 (이전 자리 계단 수의 끝 자리 수 -1, +1) 해준 수를 붙이는 경우이다. </li>
<li>ex. 2자리 계단 수를 구한다고 할 때<ul>
<li>1의 자리 계단 수 = 1, 2, 3, 4, 5, 6, 7, 8, 9<ul>
<li>1의 끝에 (1-1 = 0) 과 (1+1 = 2)를 붙인 수 -&gt; 10, 12</li>
<li>2의 끝에 (2-1 = 1) 과 (2+1 = 3)를 붙인 수 -&gt; 21, 23</li>
<li>3의 끝에 (3-1 = 2) 과 (3+1 = 4)를 붙인 수 -&gt; 32, 34</li>
<li>4의 끝에 (4-1 = 3) 과 (4+1 = 5)를 붙인 수 -&gt; 43, 45</li>
<li>5의 끝에 (5-1 = 4) 과 (5+1 = 6)를 붙인 수 -&gt; 54, 56</li>
<li>6의 끝에 (6-1 = 5) 과 (6+1 = 7)를 붙인 수 -&gt; 65, 67</li>
<li>7의 끝에 (7-1 = 6) 과 (7+1 = 8)를 붙인 수 -&gt; 76, 78</li>
<li>8의 끝에 (8-1 = 7) 과 (8+1 = 9)를 붙인 수 -&gt; 87, 89</li>
<li>9의 끝에 (9-1 = 8)를 붙인 수 -&gt; 98</li>
</ul>
</li>
</ul>
</li>
<li>위 과정을 일반화하여 <code>dp</code>를 갱신하면<ul>
<li>맨 끝이 0인 자리 수의 개수 = 이전 자리 수 중 1로 끝나는 수의 개수</li>
<li>맨 끝이 9인 자리 수의 개수 = 이전 자리 수 중 8로 끝나는 수의 개수</li>
<li>맨 끝이 j(1~8)인 자리 수의 개수 = 이전 자리 수 중 j - 1, j + 1로 끝나는 수의 개수의 합</li>
</ul>
</li>
<li>이를 <strong>점화식</strong>으로 표현하면<ul>
<li><strong>dp[i][0] = dp[i - 1][1]</strong></li>
<li><strong>dp[i][9] = dp[i - 1][8]</strong></li>
<li>*<em>(j : 1 ~ 8) dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j + 1] *</em></li>
</ul>
</li>
</ul>
<p><strong>4)</strong> dp[n]에 0 ~ 9로 끝나는 i자리 계단 수들의 개수가 각각 저장되어있으므로 해당 수들을 전부 더하면 n자리 계단 수들의 총 개수를 알 수 있다.</p>
<p>❗계단 수의 개수를 더하는 과정에서 오버플로우가 발생할 수 있기 때문에 dp를 구하는 과정마다 미리 MOD 연산을 해준다.</p>
<hr>
<h2 id="🖥️-풀이-코드">🖥️ 풀이 코드</h2>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;algorithm&gt;
using namespace std;

int n;
long long dp[101][10];        // dp[i][j] : i자리 수 계단 수 중에 맨 끝이 j인 수의 개수
const long long MOD = 1000000000;

long long solution() 
{
    // 1자리 계단수는 1 ~ 9 이므로 1 ~ 9로 끝나는 수의 개수는 각 1개씩
    for (int i = 1; i &lt;= 9; i++)
        dp[1][i] = 1;

    for (int i = 2; i &lt;= n; i++)
    {
        // 맨 끝이 0인 i자리 수의 개수 = 이전 자리(i - 1) 수 중 1로 끝나는 수의 개수
        dp[i][0] = dp[i - 1][1] % MOD;                                        
        // 맨 끝이 j(1 ~ 8)인 i자리 수의 개수 = 이전 자리(i - 1) 수 중 (j - 1)로 끝나는 수의 개수 + (j + 1)로 끝나는 수의 개수
        for (int j = 1; j &lt;= 8; j++)
            dp[i][j] = (dp[i - 1][j - 1] + dp[i - 1][j + 1]) % MOD;            
        // 맨 끝이 9인 i자리 수의 개수 = 이전 자리(i - 1) 수 중 8로 끝나는 수의 개수
        dp[i][9] = dp[i - 1][8] % MOD;        
    }

    long long maxValue = 0;
    for (int i = 0; i &lt;= 9; i++)
        maxValue = (maxValue + dp[n][i]) % MOD;

    return maxValue;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr); cout.tie(nullptr);

    cin &gt;&gt; n;
    cout &lt;&lt; solution();
}</code></pre>
]]></description>
        </item>
    </channel>
</rss>