NeFut Logo NeFut
Admin Login

Efficient State Space Search: In-Depth Analysis of Iterative Deepening and Bidirectional BFS Algorithms

Published at: 2026-05-27 09:17 Last updated: 2026-06-10 09:15
#C++ #Tutorial

Core Logic and Mathematical Principles

In high-dimensional state spaces, standard breadth-first search (BFS) can lead to memory crashes due to "state explosion," while depth-first search (DFS) often struggles in deep branches without optimal solutions.

$$\textstyle \text{Total Time Complexity} = \frac{k(k^m - 1)}{k - 1} \text{ approximately } k^m$$

This is of the same order as the time complexity of directly executing BFS, but its space complexity is only $O(m)$, fundamentally addressing the memory limit exceeded (MLE) issue caused by excessive search space.


State Design and Algorithm Derivation

Taking classic graph theory path/state transformation problems as an example. Let states be represented by integers or strings, with the standard design as follows:

1. IDS State Design and Pruning Derivation

Define the function bool dfs(int cur, int dep, int max_dep).

2. Bi-BFS Space and Meeting Position Derivation

Utilize two queues Q_start and Q_end, along with two hash tables or arrays vis_start and vis_end to maintain the number of steps expanded from both ends.


C++ Standard Source Code

Below is the standard engineering template for bidirectional breadth-first search (Bi-BFS), incorporating the space position optimization technique of "dynamically choosing the smaller queue."

#include <iostream>
#include <vector>
#include <queue>
#include <unordered_map>
#include <string>

// Demonstration of state transformation: changing one character in a string during a single transformation
std::vector<std::string> get_next_states(const std::string& u) {
    std::vector<std::string> neighbors;
    for (size_t i = 0; i < u.length(); ++i) {
        std::string v = u;
        // Simulate a certain state transformation rule (e.g., character increment)
        if (v[i] < 'z') {
            v[i]++;
            neighbors.push_back(v);
        }
    }
    return neighbors;
}

int bidirectional_bfs(const std::string& start, const std::string& target) {
    if (start == target) return 0;

    std::queue<std::string> q_start, q_end;
    // vis array records the shortest steps to reach each state from the start/end
    std::unordered_map<std::string, int> vis_start, vis_end;

    q_start.push(start);
    vis_start[start] = 0;

    q_end.push(target);
    vis_end[target] = 0;

    while (!q_start.empty() && !q_end.empty()) {
        // Core space position optimization: always choose the side with fewer states to expand
        if (q_start.size() <= q_end.size()) {
            int sz = q_start.size();
            for (int i = 0; i < sz; ++i) {
                std::string u = q_start.front();
                q_start.pop();

                int cur_dist = vis_start[u];
                std::vector<std::string> neighbors = get_next_states(u);

                for (const auto& next_state : neighbors) {
                    if (vis_start.count(next_state)) continue; // Avoid self-loop and duplicate search

                    // Critical pitfall: must check for meeting immediately upon enqueueing; checking upon dequeuing would expand one unnecessary layer
                    if (vis_end.count(next_state)) {
                        return cur_dist + 1 + vis_end[next_state];
                    }

                    vis_start[next_state] = cur_dist + 1;
                    q_start.push(next_state);
                }
            }
        } else {
            // Backward expansion logic
            int sz = q_end.size();
            for (int i = 0; i < sz; ++i) {
                std::string u = q_end.front();
                q_end.pop();

                int cur_dist = vis_end[u];
                // Note: In actual NOIP problems, the backward transformation must satisfy the inverse equivalence of the independent variable
                std::vector<std::string> neighbors = get_next_states(u); 

                for (const auto& next_state : neighbors) {
                    if (vis_end.count(next_state)) continue;

                    if (vis_start.count(next_state)) {
                        return cur_dist + 1 + vis_start[next_state];
                    }

                    vis_end[next_state] = cur_dist + 1;
                    q_end.push(next_state);
                }
            }
        }
    }
    return -1; // States are not connected
}

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

    std::string start_str, target_str;
    if (std::cin >> start_str >> target_str) {
        std::cout << bidirectional_bfs(start_str, target_str) << "\n";
    }

    return 0;
}

NOIP Practical Pitfall Guide

  1. Bi-BFS Backward Transfer Calculation Errors When expanding the endpoint in reverse during bidirectional BFS, the state transition must be the inverse of the forward transition. For example, if the forward operation is "assigning the value at position $A$ in the matrix to position $B$," the reverse operation is not the same assignment but rather the reverse of that action. If the graph is directed, the reverse BFS must run on the reverse graph. If the forward transition function is directly applied, the two searches will miss each other in high-dimensional space, leading to full queues, memory overflow, or infinite loops.
  2. IDS Forgetting to Reset Global Markers When implementing iterative deepening (IDS), after each round of increasing max_dep, since it essentially opens a new DFS tree, participants often get stuck on "not clearing the global vis markers left over from the previous round" or "global optimal solution variables." This can lead to incorrect pruning due to residual markers from the previous round when entering the first layer of the second round, resulting in a return of no solution and a direct loss of the entire score for the problem.

Classic NOIP/Luogu Problems

1. Luogu P2326 Moving Toys

2. Luogu P1074 Target Sudoku

Original Source: local://4.2

[h] Back to Home