NeFut Logo NeFut
Admin Login

Decoding Impartial Combinatorial Games: Analysis of P and N States in Game Theory

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

Core Logic and Mathematical Principles

Game Theory is a branch of discrete mathematics that studies decision-making interactions with adversarial properties. In the NOIP competition, the focus is heavily on Impartial Combinatorial Games (ICG).

1. Characteristics of Impartial Combinatorial Games (ICG)

A game is classified as an impartial combinatorial game if it satisfies the following stringent constraints:

2. State Space Division: P-position and N-position

In the topological graph of the state space of ICG, all game states can be strictly and uniquely divided into two categories:

Three Axioms for State Determination

Based on backward induction, the essence of the state is locked by the following three foundational logics:

  1. Terminal states must be P states: All terminal states that cannot make any legal moves are P-positions (according to the rule that the player who cannot move loses).
  2. Reachable P must be N: If from the current state there exists at least one legal move that can transition to a P-position, then the current state is an N-position (the current player simply needs to decisively pass this losing state to the opponent to win).
  3. All successors being N must be P: If from the current state, regardless of the legal move made, all resulting successor states are N-positions, then the current state is a P-position (no matter how the current player struggles, the opponent faces winning states).

Algorithm Derivation and State Design

Topological Memoization Search of State Graphs

For relatively simple games where states can be quantified numerically, we can abstract all possible game states as nodes on a directed acyclic graph (DAG), with legal moves abstracted as directed edges.

$$f[u] = \bigvee_{(u, v) \in E} \big( f[v] == 0 \big)$$

Algebraically, this means performing a logical OR operation over all successor nodes $v$ of node $u$. If at least one of the successors is a losing state ($f[v]==0$), then the current state is a winning state ($f[u]=1$); if all successors are winning states, then the current state is a losing state.


C++ Standard Source Code

The following source code demonstrates how to determine P/N states in a typical game state graph using memoization.

#include <iostream>
#include <vector>
#include <cstring>

const int MAXS = 10000;
int f[MAXS + 5]; // State memoization array: -1 indicates uncalculated, 0 indicates P-position, 1 indicates N-position

// Get all legal successor states for the current state (example: can subtract 1, 3, or 4 each time)
std::vector<int> get_next_states(int state) {
    std::vector<int> next_states;
    if (state >= 1) next_states.push_back(state - 1);
    if (state >= 3) next_states.push_back(state - 3);
    if (state >= 4) next_states.push_back(state - 4);
    return next_states;
}

// Memoization search to determine P/N states
int judge_position(int state) {
    if (f[state] != -1) return f[state]; // Intercept already calculated states

    std::vector<int> next_moves = get_next_states(state);

    // Boundary axiom 1: terminal states that cannot move must be P-position
    if (next_moves.empty()) {
        f[state] = 0;
        return 0;
    }

    // Iterate through all possible successor moves
    for (int next_state : next_moves) {
        // Axiom 2: if there exists a losing state (P) among successor states, the current state becomes a winning state (N)
        if (judge_position(next_state) == 0) {
            f[state] = 1;
            return 1;
        }
    }

    // Axiom 3: if all successor moves lead to winning states, the current player has no escape, the current state is a losing state (P)
    f[state] = 0;
    return 0;
}

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

    std::memset(f, -1, sizeof(f)); // Initialize state table to -1

    int t;
    if (std::cin >> t) {
        while (t--) {
            int initial_state;
            std::cin >> initial_state;

            if (judge_position(initial_state) == 1) {
                std::cout << "First player wins (N-position)\n";
            } else {
                std::cout << "Second player wins (P-position)\n";
            }
        }
    }
    return 0;
}

NOIP Practical Pitfall Guide

  1. Confusing P states (losing) with N states (winning) in algebraic conclusions When writing game theory searches, participants often mistakenly reverse judgment conditions. It is imperative to remember: it is N only if successors include P. Many contestants incorrectly write "all successors must be P for it to be N", which completely flips the victory and defeat logic of the entire game tree.
  2. Forgetting to reset or incorrectly resetting the memoization array under multiple queries If the rules of the game (e.g., the set of stones that can be taken each time) change between different input data, then the f array state obtained from the last query must not be reused in the next query. It is essential to use std::memset(f, -1, sizeof(f)) to thoroughly reset the array for each test input; otherwise, serious cross-data state contamination may occur.

Classic NOIP/Luogu Problems

1. Luogu P1290 Golden Ratio Game

2. Luogu P2148 [SDOI2009] E&D

Original Source: local://22.1

[h] Back to Home