NeFut Logo NeFut
Admin Login

Deep Dive into the Perfect Combination of Memoization and Topological Sorting

Published at: 2026-05-27 09:17 Last updated: 2026-06-06 13:04
#algorithm #Dynamic Programming #DP

Core Logic and Mathematical Principles

For many complex or hidden state spaces, the dependencies between states are not as intuitive as linear loops. If nested loops are forced, it is easy to lead to the use of uninitialized states due to the difficulty in determining the computation order.

Memoization and Topological Sorting are philosophically equivalent:

  1. Essence of Topological Order: If the computation of state $A$ depends on state $B$, then there must be a directed edge $B \to A$ in the state graph (DAG). The purpose of topological sorting is to establish a total order such that all starting points of edges are placed before their endpoints.
  2. Recursive Form of Topological Sorting (DFS): Memoization implicitly completes the topological sorting of the state graph using the system stack of Depth-First Search (DFS) from a reverse perspective of dynamic programming (starting from the target state and recursively querying predecessor states). When all successor/predecessor states of a state have been visited and are about to be popped from the stack, the global optimal solution for that state has been solidified.
  3. Core Operator to Eliminate Redundancy: Memoization introduces a memoization array f that is isomorphic to the state space (usually initialized to an unvisited marker, such as -1). In the recursive branches, once it is found that f[state] has already been computed, it immediately returns in $O(1)$. Geometrically, this is equivalent to directly cutting off all subgraphs that have been visited multiple times in the DAG, forcing the exponential complexity of the search back to the sum of the number of vertices and edges in the graph: $O(|V| + |E|)$.

State Design and Algorithm Derivation

Taking the classic graph theory model "Skiing" (the longest path in a DAG) as an example, we derive the execution process of memoization:

1. State Design

Let state $f[i][j]$ represent the maximum area length that can be skied starting from the grid coordinates $(i, j)$ (i.e., the number of edges in the longest path starting from that grid point $+ 1$).

2. Transition Equation and Recursive Form

Since one can only ski in lower terrain directions, define $(nx, ny)$ as the neighbors of $(i, j)$ that satisfy $h[nx][ny] < h[i][j]$. The transition equation is expressed as a typical successor maximum relaxation:

$$f[i][j] = 1 + \max_{(nx, ny) \in \text{valid\_neighbors}(i, j)} \{ f[nx][ny] \}$$

If there are no lower terrains around the current point, this grid point acts as a source point with an in-degree of 0 in the DAG (considered as a terminal point in reverse), $f[i][j] = 1$.

3. Memoization Logic

Define the search function int dfs(int x, int y):


C++ Standard Source Code (NOIP Style)

The following is the implementation of the longest descending path based on memoization, strictly compiled according to Linux g++ -O2 standards.

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

const int MAXN = 105;
int h[MAXN][MAXN];
int f[MAXN][MAXN]; // Memoization array, 0 indicates not accessed
int n, m;

// Direction vector arrays
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};

int dfs(int x, int y) {
    // Fatal pitfall: if already computed, must return directly, otherwise memoization fails and degenerates into pure brute force search
    if (f[x][y] != 0) {
        return f[x][y];
    }

    f[x][y] = 1; // Initialize: the node itself counts as at least one length

    for (int i = 0; i < 4; ++i) {
        int nx = x + dx[i];
        int ny = y + dy[i];

        // Boundary check and strict descent condition
        if (nx >= 1 && nx <= n && ny >= 1 && ny <= m) {
            if (h[nx][ny] < h[x][y]) {
                f[x][y] = max(f[x][y], dfs(nx, ny) + 1); // Implicit topological order transition
            }
        }
    }

    return f[x][y]; // At this point, the longest path for this node in the DAG has been completely solidified
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    if (!(cin >> n >> m)) return 0;

    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            cin >> h[i][j];
            f[i][j] = 0; // 0 initialization indicates all states are unvalued
        }
    }

    int ans = 0;
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            ans = max(ans, dfs(i, j)); // Multi-source DFS to find the global longest path
        }
    }

    cout << ans << "\n";

    return 0;
}

NOIP Practical Pitfall Guide

1. Memoization Array Not Assigned Correct "Unvisited" Marker Value

2. Hidden Loops in State Transition Graph Cause System Stack Overflow


Classic NOIP/Luogu Problems

1. Luogu P1434 [SHOI2002] Skiing

2. Luogu P1464 Function

Original Source: local://13.3

[h] Back to Home