<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Tars' Thoughts]]></title><description><![CDATA[Tars' Thoughts]]></description><link>https://tarshawkman.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Wed, 02 Sep 2026 06:25:45 GMT</lastBuildDate><atom:link href="https://tarshawkman.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[DP: A complex term for memoization]]></title><description><![CDATA[Dynamic Programming can feel confusing because every problem looks different.
But many DP problems follow the same patterns.
In this blog, we will look at 5 common forms of DP:

Knapsack Form

Ending ]]></description><link>https://tarshawkman.hashnode.dev/dynamic-programming-5-common-dp-patterns</link><guid isPermaLink="true">https://tarshawkman.hashnode.dev/dynamic-programming-5-common-dp-patterns</guid><category><![CDATA[Dynamic Programming]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[algorithms]]></category><category><![CDATA[Competitive programming]]></category><category><![CDATA[data structure and algorithms ]]></category><category><![CDATA[data structures]]></category><category><![CDATA[coding]]></category><dc:creator><![CDATA[Aaditya Saraf]]></dc:creator><pubDate>Mon, 31 Aug 2026 18:51:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6903ce3671310108347aa691/4e72040b-f7a5-48ab-b54b-c7751a2b6323.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Dynamic Programming can feel confusing because every problem looks different.</p>
<p>But many DP problems follow the same patterns.</p>
<p>In this blog, we will look at <strong>5 common forms of DP</strong>:</p>
<ol>
<li><p>Knapsack Form</p>
</li>
<li><p>Ending Form</p>
</li>
<li><p>Multi-sequence / Matching Form</p>
</li>
<li><p>Interval DP</p>
</li>
<li><p>Game DP</p>
</li>
</ol>
<p>The main idea is simple:</p>
<blockquote>
<p>Before writing the code, try to identify which DP form the problem belongs to.</p>
</blockquote>
<p>Once the form is identified, finding the <strong>state</strong> and <strong>transition</strong> becomes much easier.</p>
<hr />
<h1>1. Knapsack Form</h1>
<p>This is probably the most common DP form.</p>
<p>You usually have something like:</p>
<ul>
<li><p>An array</p>
</li>
<li><p>A set of items</p>
</li>
<li><p>Some choices</p>
</li>
</ul>
<p>For every item, we usually have two options:</p>
<ul>
<li><p>Take it</p>
</li>
<li><p>Don't take it</p>
</li>
</ul>
<p>For example:</p>
<blockquote>
<p>Given some items and a limited capacity, find the maximum value.</p>
</blockquote>
<p>At every index, we decide:</p>
<pre><code class="language-text">Take the current item
OR
Skip the current item
</code></pre>
<p>So the transition usually looks like:</p>
<pre><code class="language-text">dp(i, remaining) =
    max(
        take current item,
        skip current item
    )
</code></pre>
<p>The exact meaning of <code>remaining</code> depends on the problem.</p>
<p>It can be:</p>
<ul>
<li><p>Remaining capacity</p>
</li>
<li><p>Remaining sum</p>
</li>
<li><p>Remaining operations</p>
</li>
<li><p>Some other restriction</p>
</li>
</ul>
<p>The important thing is:</p>
<blockquote>
<p>Knapsack problems usually have a <strong>take / not take</strong> choice.</p>
</blockquote>
<hr />
<h2>Example State</h2>
<pre><code class="language-text">dp(i, capacity)
</code></pre>
<p>This can mean:</p>
<blockquote>
<p>Maximum answer we can get using items from index <code>i</code> onwards with <code>capacity</code> left.</p>
</blockquote>
<p>For every item:</p>
<pre><code class="language-text">Skip:
dp(i + 1, capacity)

Take:
value[i] + dp(i + 1, capacity - weight[i])
</code></pre>
<p>Then:</p>
<pre><code class="language-text">dp(i, capacity) =
max(
    dp(i + 1, capacity),
    value[i] + dp(i + 1, capacity - weight[i])
)
</code></pre>
<hr />
<h1>2. Ending Form</h1>
<p>The second form is used when we are building a:</p>
<ul>
<li><p>Subsequence</p>
</li>
<li><p>Partition</p>
</li>
<li><p>Sequence</p>
</li>
</ul>
<p>The important idea here is:</p>
<blockquote>
<p>Instead of asking for the answer of the whole array, ask for the answer <strong>ending at index</strong> <code>i</code>.</p>
</blockquote>
<p>For example:</p>
<pre><code class="language-text">dp(i)
</code></pre>
<p>can mean:</p>
<blockquote>
<p>Longest valid subsequence ending at index <code>i</code>.</p>
</blockquote>
<p>This makes the transition easier.</p>
<hr />
<h2>Example: Longest Increasing Subsequence</h2>
<p>Suppose:</p>
<pre><code class="language-text">arr[j] &lt; arr[i]
</code></pre>
<p>Then we can put <code>arr[i]</code> after the subsequence ending at <code>j</code>.</p>
<p>So:</p>
<pre><code class="language-text">dp(i) = max(dp(j) + 1)
</code></pre>
<p>for every:</p>
<pre><code class="language-text">j &lt; i
</code></pre>
<p>such that:</p>
<pre><code class="language-text">arr[j] &lt; arr[i]
</code></pre>
<p>The base case is:</p>
<pre><code class="language-text">dp(i) = 1
</code></pre>
<p>because every element can form a subsequence of length <code>1</code>.</p>
<p>So the full transition becomes:</p>
<pre><code class="language-text">dp(i) = 1

for every j &lt; i:
    if arr[j] &lt; arr[i]:
        dp(i) = max(dp(i), dp(j) + 1)
</code></pre>
<p>The final answer is:</p>
<pre><code class="language-text">max(dp(i))
</code></pre>
<p>for every <code>i</code>.</p>
<hr />
<h2>Extra States</h2>
<p>Sometimes just <code>dp(i)</code> is not enough.</p>
<p>We may need extra information.</p>
<p>For example:</p>
<pre><code class="language-text">dp(i, peakFound)
</code></pre>
<p>This can mean:</p>
<blockquote>
<p>Longest sequence ending at <code>i</code>, and whether a peak has already been found or not.</p>
</blockquote>
<p>This is useful in problems like Longest Bitonic Subsequence.</p>
<p>So the Ending Form usually looks like:</p>
<pre><code class="language-text">dp(index, some extra restriction)
</code></pre>
<p>The notes describe this form as building a subsequence or partition and using a state based on what ends at the current position.</p>
<hr />
<h1>3. Multi-sequence / Matching Form</h1>
<p>This form is used when we have:</p>
<ul>
<li><p>Two strings</p>
</li>
<li><p>Two arrays</p>
</li>
<li><p>Multiple sequences</p>
</li>
</ul>
<p>The most common example is:</p>
<blockquote>
<p>Longest Common Subsequence</p>
</blockquote>
<p>Suppose we have:</p>
<pre><code class="language-text">s = "abc"
t = "ac"
</code></pre>
<p>We define:</p>
<pre><code class="language-text">dp(i, j)
</code></pre>
<p>as:</p>
<blockquote>
<p>Answer using the sequences starting from index <code>i</code> and <code>j</code>.</p>
</blockquote>
<p>Now there are two cases.</p>
<hr />
<h2>Case 1: Characters are Equal</h2>
<p>If:</p>
<pre><code class="language-text">s[i] == t[j]
</code></pre>
<p>Then we can use both characters.</p>
<p>So:</p>
<pre><code class="language-text">dp(i, j) = 1 + dp(i + 1, j + 1)
</code></pre>
<hr />
<h2>Case 2: Characters are Different</h2>
<p>If:</p>
<pre><code class="language-text">s[i] != t[j]
</code></pre>
<p>Then we try skipping one character.</p>
<pre><code class="language-text">dp(i, j) =
max(
    dp(i + 1, j),
    dp(i, j + 1)
)
</code></pre>
<p>The state is usually:</p>
<pre><code class="language-text">dp(i, j)
</code></pre>
<p>because we are tracking positions in two sequences.</p>
<p>The notes use LCS as the main example for this form and define the state using positions in both sequences.</p>
<hr />
<h2>Complexity</h2>
<p>If both sequences have length <code>N</code> and <code>M</code>:</p>
<pre><code class="language-text">States = N × M
</code></pre>
<p>Each state is calculated once.</p>
<p>So:</p>
<pre><code class="language-text">Time Complexity = O(NM)
Memory = O(NM)
</code></pre>
<hr />
<h1>4. Interval DP</h1>
<p>Interval DP is also called:</p>
<ul>
<li><p>Range DP</p>
</li>
<li><p>LR DP</p>
</li>
</ul>
<p>Here, instead of working on one index, we work on a range.</p>
<p>The state usually looks like:</p>
<pre><code class="language-text">dp(L, R)
</code></pre>
<p>This means:</p>
<blockquote>
<p>Answer for the part of the array from <code>L</code> to <code>R</code>.</p>
</blockquote>
<p>The notes describe this form as <strong>LRDP / Interval DP</strong> and mention three common styles: <strong>Delete and Squeeze, Merge, and Group Delete</strong>.</p>
<hr />
<h2>Example: Burst Balloons</h2>
<p>Suppose we have:</p>
<pre><code class="language-text">3 1 5 8
</code></pre>
<p>When we burst a balloon, the answer depends on its neighbours.</p>
<p>This makes the problem difficult because the neighbours keep changing.</p>
<p>So instead of thinking:</p>
<blockquote>
<p>Which balloon should I burst first?</p>
</blockquote>
<p>We think:</p>
<blockquote>
<p>Which balloon should I burst last?</p>
</blockquote>
<p>This is the main trick.</p>
<hr />
<h2>State</h2>
<pre><code class="language-text">dp(L, R)
</code></pre>
<p>means:</p>
<blockquote>
<p>Maximum coins we can get by bursting balloons from <code>L</code> to <code>R</code>.</p>
</blockquote>
<p>Now assume balloon <code>mid</code> is the <strong>last balloon burst</strong> inside this range.</p>
<p>Before bursting <code>mid</code>:</p>
<ul>
<li><p>Everything from <code>L</code> to <code>mid - 1</code> is already removed.</p>
</li>
<li><p>Everything from <code>mid + 1</code> to <code>R</code> is already removed.</p>
</li>
</ul>
<p>So we get:</p>
<pre><code class="language-text">dp(L, mid - 1)
+
dp(mid + 1, R)
</code></pre>
<p>Then we add the coins for bursting <code>mid</code>.</p>
<p>So:</p>
<pre><code class="language-text">dp(L, R) =
max over every mid:

dp(L, mid - 1)
+
dp(mid + 1, R)
+
coins from bursting mid
</code></pre>
<p>This is the main idea behind many Interval DP problems.</p>
<p>The Burst Balloons notes specifically use the idea of choosing the balloon deleted last inside a range.</p>
<hr />
<h2>Another Way to Identify Interval DP</h2>
<p>Ask yourself:</p>
<blockquote>
<p>Is the problem asking me to perform operations on a subarray or range?</p>
</blockquote>
<p>For example:</p>
<pre><code class="language-text">[L, R]
</code></pre>
<p>Then try:</p>
<pre><code class="language-text">dp(L, R)
</code></pre>
<p>Also check if:</p>
<ul>
<li><p>We split the range.</p>
</li>
<li><p>We delete something from the range.</p>
</li>
<li><p>We merge two parts.</p>
</li>
</ul>
<p>If yes, it might be Interval DP.</p>
<hr />
<h1>5. Game DP</h1>
<p>Game DP problems usually have:</p>
<ul>
<li><p>Two players</p>
</li>
<li><p>Alternate turns</p>
</li>
<li><p>Some possible moves</p>
</li>
</ul>
<p>Usually, the question is:</p>
<blockquote>
<p>Can the first player win?</p>
</blockquote>
<p>The notes classify these as combinatorial impartial two-player games.</p>
<p>The most important idea is very simple.</p>
<hr />
<h2>Winning and Losing States</h2>
<p>Suppose it is your turn.</p>
<p>If you have <strong>at least one move</strong> that puts your opponent in a losing state:</p>
<pre><code class="language-text">You are in a winning state.
</code></pre>
<p>If <strong>every possible move</strong> puts your opponent in a winning state:</p>
<pre><code class="language-text">You are in a losing state.
</code></pre>
<p>That is basically the whole idea behind many Game DP problems.</p>
<p>The notes state the same winning/losing rule for Game DP.</p>
<hr />
<h2>Example</h2>
<p>Suppose there are <code>N</code> marbles.</p>
<p>A player can take:</p>
<pre><code class="language-text">1 marble
OR
2 marbles
</code></pre>
<p>Define:</p>
<pre><code class="language-text">dp(n)
</code></pre>
<p>as:</p>
<blockquote>
<p>Can the current player win with <code>n</code> marbles left?</p>
</blockquote>
<p>Now:</p>
<pre><code class="language-text">dp(n) = true
</code></pre>
<p>if we can make a move to a losing state.</p>
<p>So:</p>
<pre><code class="language-text">if dp(n - 1) == false:
    dp(n) = true

if dp(n - 2) == false:
    dp(n) = true
</code></pre>
<p>Otherwise:</p>
<pre><code class="language-text">dp(n) = false
</code></pre>
<hr />
<h1>How to Identify the DP Form</h1>
<p>When you see a DP problem, don't immediately start writing:</p>
<pre><code class="language-text">dp[i]
</code></pre>
<p>First ask:</p>
<h3>Question 1</h3>
<p>Do I have a choice like:</p>
<pre><code class="language-text">Take
OR
Don't take?
</code></pre>
<p>Then it might be:</p>
<pre><code class="language-text">Knapsack Form
</code></pre>
<hr />
<h3>Question 2</h3>
<p>Am I building a:</p>
<pre><code class="language-text">Subsequence
OR
Partition?
</code></pre>
<p>Then it might be:</p>
<pre><code class="language-text">Ending Form
</code></pre>
<p>Try defining:</p>
<pre><code class="language-text">dp(i) = answer ending at i
</code></pre>
<hr />
<h3>Question 3</h3>
<p>Am I working with:</p>
<pre><code class="language-text">Two strings?
Two arrays?
Two sequences?
</code></pre>
<p>Then it might be:</p>
<pre><code class="language-text">Matching / Multi-sequence DP
</code></pre>
<p>Try:</p>
<pre><code class="language-text">dp(i, j)
</code></pre>
<hr />
<h3>Question 4</h3>
<p>Am I performing operations on:</p>
<pre><code class="language-text">A subarray [L, R]?
</code></pre>
<p>Then it might be:</p>
<pre><code class="language-text">Interval DP
</code></pre>
<p>Try:</p>
<pre><code class="language-text">dp(L, R)
</code></pre>
<hr />
<h3>Question 5</h3>
<p>Is this a:</p>
<pre><code class="language-text">Two-player game?
</code></pre>
<p>Then it might be:</p>
<pre><code class="language-text">Game DP
</code></pre>
<p>Try to identify:</p>
<pre><code class="language-text">Winning states
Losing states
</code></pre>
<hr />
<h1>A Simple DP Checklist</h1>
<p>Whenever I solve a DP problem, I try to find these things:</p>
<h2>1. What does my DP state mean?</h2>
<p>For example:</p>
<pre><code class="language-text">dp(i)
</code></pre>
<p>or:</p>
<pre><code class="language-text">dp(i, j)
</code></pre>
<p>or:</p>
<pre><code class="language-text">dp(L, R)
</code></pre>
<p>The most important thing is:</p>
<blockquote>
<p>You should be able to explain the meaning of your state in one sentence.</p>
</blockquote>
<hr />
<h2>2. What choices do I have?</h2>
<p>For example:</p>
<pre><code class="language-text">Take / Don't take
</code></pre>
<p>or:</p>
<pre><code class="language-text">Move forward
</code></pre>
<p>or:</p>
<pre><code class="language-text">Split at mid
</code></pre>
<p>or:</p>
<pre><code class="language-text">Choose one move in a game
</code></pre>
<hr />
<h2>3. What is the base case?</h2>
<p>Ask:</p>
<blockquote>
<p>When does the problem become so small that I already know the answer?</p>
</blockquote>
<hr />
<h2>4. What is the transition?</h2>
<p>Ask:</p>
<blockquote>
<p>How can I build the answer for the current state using smaller states?</p>
</blockquote>
<hr />
<h2>5. What is the final answer?</h2>
<p>Sometimes it is:</p>
<pre><code class="language-text">dp(n)
</code></pre>
<p>Sometimes:</p>
<pre><code class="language-text">max(dp(i))
</code></pre>
<p>Sometimes:</p>
<pre><code class="language-text">dp(0, n - 1)
</code></pre>
<p>It depends on the problem.</p>
<hr />
<h1>Final Thoughts</h1>
<p>Dynamic Programming problems can look very different on the surface.</p>
<p>But many of them follow the same patterns.</p>
<p>The five forms are:</p>
<pre><code class="language-text">1. Knapsack
   → Take / Don't take

2. Ending Form
   → Build a subsequence or partition

3. Matching Form
   → Work with multiple sequences

4. Interval DP
   → Work on a range [L, R]

5. Game DP
   → Winning and losing states
</code></pre>
<p>So next time you see a DP problem, first try to identify its form.</p>
<p>Once you know the form, you can usually start thinking about:</p>
<pre><code class="language-text">State
→ Choices
→ Transition
→ Base Case
→ Answer
</code></pre>
<p>That's it. Cheerio!</p>
]]></content:encoded></item><item><title><![CDATA[Binary Lifting]]></title><description><![CDATA[The idea is to break a given parameter, say k, into its binary representation, and then traverse over its set bits.
Instead of moving one step at a time, we jump in powers of two. This reduces the tra]]></description><link>https://tarshawkman.hashnode.dev/binary-lifting</link><guid isPermaLink="true">https://tarshawkman.hashnode.dev/binary-lifting</guid><dc:creator><![CDATA[Aaditya Saraf]]></dc:creator><pubDate>Wed, 29 Jul 2026 19:33:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6903ce3671310108347aa691/76ae659f-b9b2-4a04-b145-bd75eaada4b7.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The idea is to break a given parameter, say <code>k</code>, into its binary representation, and then traverse over its set bits.</p>
<p>Instead of moving one step at a time, we jump in powers of two. This reduces the traversal time complexity to <strong>O(log N)</strong> and is generally used in graph/tree problems.</p>
<p>For example, let</p>
<pre><code class="language-text">par[i][k] = 2^k-th parent of node i
</code></pre>
<p>Then moving <code>k</code> levels up becomes:</p>
<pre><code class="language-cpp">for (int j = 19; j &gt;= 0; j--) {

    if (k &amp; (1 &lt;&lt; j)) {      // check if j-th bit is set
        i = par[i][j];       // binary lifting step
    }

}
</code></pre>
<hr />
<h2>Path Aggregates</h2>
<p>We can also use binary lifting to pre-compute <strong>path aggregates</strong> while traversing a tree.</p>
<p>The idea is exactly the same—we just store some extra information along with every jump.</p>
<h2>Precomputation for Path Aggregates</h2>
<h3>1. Base Case</h3>
<p>For <code>j = 0</code>, the <code>2^0</code>-th ancestor is simply the immediate parent.</p>
<p>So,</p>
<pre><code class="language-text">agg[u][0]
</code></pre>
<p>stores the property of the direct edge (or node value, depending on the problem) between <code>u</code> and its parent.</p>
<hr />
<h3>2. Recursive Relation</h3>
<p>For <code>j &gt; 0</code>, the aggregate for the <code>2^j</code>-th ancestor is obtained by combining two <code>2^(j-1)</code> jumps.</p>
<pre><code class="language-text">agg[u][j] = combine(
                agg[u][j-1],
                agg[parent[u][j-1]][j-1]
            )
</code></pre>
<p>The <code>combine()</code> function depends on what property you're computing.</p>
<p>For example,</p>
<ul>
<li><strong>Sum</strong></li>
</ul>
<pre><code class="language-text">combine(x, y) = x + y
</code></pre>
<ul>
<li><strong>GCD</strong></li>
</ul>
<pre><code class="language-text">combine(x, y) = gcd(x, y)
</code></pre>
<ul>
<li><strong>Maximum</strong></li>
</ul>
<pre><code class="language-text">combine(x, y) = max(x, y)
</code></pre>
<hr />
<h2>Code Example (Path GCD)</h2>
<pre><code class="language-cpp">vector&lt;vector&lt;int&gt;&gt; adj;
vector&lt;vector&lt;int&gt;&gt; parent, dp;
vector&lt;int&gt; depth, arr;

int n;
const int LOG = 20;

// DFS to compute depth and binary lifting table
void dfs(int node, int par, int dep) {

    depth[node] = dep;
    parent[node][0] = par;
    dp[node][0] = arr[node];

    // Build parent and aggregate tables
    for (int i = 1; i &lt; LOG; i++) {
        parent[node][i] = parent[parent[node][i - 1]][i - 1];
        dp[node][i] = __gcd(
            dp[node][i - 1],
            dp[parent[node][i - 1]][i - 1]
        );
    }

    for (auto v : adj[node]) {
        if (v != par)
            dfs(v, node, dep + 1);
    }
}
</code></pre>
<hr />
<h2>Path GCD Query</h2>
<pre><code class="language-cpp">int pathGCD(int u, int v) {

    if (depth[u] &lt; depth[v])
        swap(u, v);

    int ans = 0;

    // Bring u to the same level as v
    int diff = depth[u] - depth[v];

    for (int i = LOG - 1; i &gt;= 0; i--) {

        if (diff &amp; (1 &lt;&lt; i)) {
            ans = __gcd(ans, dp[u][i]);
            u = parent[u][i];
        }

    }

    // If u == v, we've already reached the LCA.
    // GCD has been calculated just before this node.
    if (u == v)
        return __gcd(ans, arr[u]);

    // Lift both nodes together until just below the LCA
    for (int i = LOG - 1; i &gt;= 0; i--) {

        if (parent[u][i] != parent[v][i]) {

            ans = __gcd(ans, dp[u][i]);
            ans = __gcd(ans, dp[v][i]);

            u = parent[u][i];
            v = parent[v][i];
        }
    }

    // We stopped one step before the LCA,
    // so we still need to include u, v and the LCA itself.

    ans = __gcd(ans, arr[u]);
    ans = __gcd(ans, arr[v]);

    return __gcd(ans, arr[parent[u][0]]);
}
</code></pre>
<hr />
<h2>Complexity</h2>
<table>
<thead>
<tr>
<th>Operation</th>
<th>Complexity</th>
</tr>
</thead>
<tbody><tr>
<td>Preprocessing</td>
<td><strong>O(N log N)</strong></td>
</tr>
<tr>
<td>Query</td>
<td><strong>O(log N)</strong></td>
</tr>
<tr>
<td>Memory</td>
<td><strong>O(N log N)</strong></td>
</tr>
</tbody></table>
]]></content:encoded></item></channel></rss>