Dynamic Programming Essentials: Linear Recurrence, Constrained Optimization, and Probabilistic Models

This problem involves computing the minimal cost to merge points into a connected component using a divide-and-conquer DP approach.

Key Insights

The recurrence relation stems from optimal substructure:

  • For even counts: The optimal strategy splits the points into two equal halves
  • For odd counts: The optimal strategy splits into nearly equal halves (⌊n/2⌋ and ⌈n/2⌉)

The cost function follows: cost[n] = cost[n/2] + cost[n/2 + n%2] + (n%2)

Algorithm Design

We employ memoized recursion to avoid recomputation. The base cases are trivial (0 or 1 point requires zero cost).

Implementation

#include <iostream>
#include <unordered_map>
using namespace std;

class Solution {
private:
    unordered_map<long long, long long> memoTable;
    
    long long computeCost(long long points) {
        if (memoTable.count(points)) return memoTable[points];
        if (points <= 1) return memoTable[points] = 0;
        
        long long half = points / 2;
        if (points % 2 == 0) {
            long long subCost = computeCost(half);
            return memoTable[points] = 2 * subCost;
        } else {
            long long lowerHalf = computeCost(half);
            long long upperHalf = computeCost(half + 1);
            return memoTable[points] = lowerHalf + upperHalf + 1;
        }
    }
    
public:
    void solve() {
        int testCases;
        cin >> testCases;
        while (testCases--) {
            long long totalPoints;
            cin >> totalPoints;
            cout << computeCost(totalPoints) << endl;
        }
    }
};

int main() {
    Solution solver;
    solver.solve();
    return 0;
}

Problem 2: Floral Arrangement Display

A constrained linear DP problem where we must select optimal positions for flowers across display windows.

DP State Definition

Let dp[row][col] represent the maximum aesthetic value achievable when placing a flower at row row and column col.

Critical Initialization

The initialization requires careful boundary conditions: if (row == totalRows && col >= totalRows). A common mistake is omitting the column constraint, leading to invalid states.

State Transition

We proces rows from bottom to top, ensuring each subsequent flower is placed to the right of the previous one:

for (int r = rows-1; r >= 1; --r) {
    for (int c = 1; c <= cols - (rows - r); ++c) {
        for (int next = c+1; next <= cols; ++next) {
            dp[r][c] = max(dp[r][c], value[r][c] + dp[r+1][next]);
        }
    }
}

Complete Solution

#include <bits/stdc++.h>
using namespace std;

const int MAX_DIM = 110;
const long long NEG_INF = LLONG_MIN;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int rows, cols;
    if (!(cin >> rows >> cols)) return 0;
    
    long long value[MAX_DIM][MAX_DIM];
    long long dp[MAX_DIM][MAX_DIM];
    int nextPos[MAX_DIM][MAX_DIM];
    
    // Input and initialization
    for (int i = 1; i <= rows; ++i) {
        for (int j = 1; j <= cols; ++j) {
            cin >> value[i][j];
            dp[i][j] = NEG_INF;
            nextPos[i][j] = j;
            if (i == rows && j >= rows) {
                dp[i][j] = value[i][j];
            }
        }
    }
    
    // DP computation
    for (int r = rows - 1; r >= 1; --r) {
        for (int c = 1; c <= cols - (rows - r); ++c) {
            for (int k = c + 1; k <= cols; ++k) {
                if (dp[r+1][k] != NEG_INF) {
                    long long candidate = value[r][c] + dp[r+1][k];
                    if (candidate > dp[r][c]) {
                        dp[r][c] = candidate;
                        nextPos[r][c] = k;
                    }
                }
            }
        }
    }
    
    // Find optimal starting position
    long long bestValue = dp[1][1];
    int startCol = 1;
    for (int j = 2; j <= cols; ++j) {
        if (dp[1][j] > bestValue) {
            bestValue = dp[1][j];
            startCol = j;
        }
    }
    
    cout << bestValue << "\n";
    
    // Reconstruct path
    int currentCol = startCol;
    for (int r = 1; r <= rows; ++r) {
        cout << currentCol << " ";
        currentCol = nextPos[r][currentCol];
    }
    
    return 0;
}

Problem 3: Ball Drop Probability

A probability DP problem simulating ball movement through a peg board with missing pegs.

Modeling Approach

We model the probability distribution as balls fall through n rows of pegs. Each cell (i,j) can contain a peg (*) or be empty.

Transition Rules

  • With peg: Ball splits equally to (i+1,j) and (i+1,j+1)
  • Without peg: Ball falls straight to (i+2,j+1) with quadruple weight (or double if hitting bottom)

Implementation Notes

The DP table stores weighted counts rather than raw probabilities to maintain precision. We reduce the final fraction by dividing common powers of 2.

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

const int MAX_LEVELS = 55;

long long computeGCD(long long a, long long b) {
    while (b != 0) {
        long long temp = b;
        b = a % b;
        a = temp;
    }
    return a;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int levels, target;
    cin >> levels >> target;
    
    vector<string> board(levels + 3, string(levels + 3, ' '));
    char ch;
    for (int i = 1; i <= levels; ++i) {
        for (int j = 1; j <= i; ++j) {
            cin >> ch;
            board[i][j] = ch;
        }
    }
    
    long long prob[MAX_LEVELS][MAX_LEVELS] = {0};
    prob[1][1] = 1LL;
    
    for (int row = 1; row <= levels; ++row) {
        for (int col = 1; col <= row; ++col) {
            long long current = prob[row][col];
            if (current == 0) continue;
            
            if (board[row][col] == '*') {
                // With peg: split equally
                prob[row + 1][col] += current;
                prob[row + 1][col + 1] += current;
            } else {
                // Without peg: fall through
                if (row + 2 <= levels + 1) {
                    prob[row + 2][col + 1] += 4 * current;
                } else {
                    prob[levels + 1][col + 1] += 2 * current;
                }
            }
        }
    }
    
    long long totalWeight = 0;
    for (int i = 1; i <= levels + 1; ++i) {
        totalWeight += prob[levels + 1][i];
    }
    
    long long targetWeight = prob[levels + 1][target + 1];
    
    // Simplify fraction by removing powers of 2
    while ((totalWeight % 2 == 0) && (targetWeight % 2 == 0)) {
        totalWeight /= 2;
        targetWeight /= 2;
    }
    
    cout << targetWeight << "/" << totalWeight;
    return 0;
}

Tags: dynamic-programming cpp probability-dp memoization linear-dp

Posted on Sun, 13 Sep 2026 16:28:08 +0000 by hr8886