Alex's Anthology of Algorithms Common Code for Contests in Concise C++
Mathematics / Game Theory

6.7.4 Grundy Numbers on DAG

6-Mathematics/6.7.4_Grundy_Numbers_on_DAG.cpp

Computes Grundy numbers for impartial games whose positions form a directed acyclic graph. Each vertex is a game position, and each outgoing edge is a legal move. Terminal vertices have Grundy number 0; all other vertices take the MEX of their successors' Grundy numbers.

The implementation uses memoized DFS. It assumes the graph is acyclic; if cycles are present, the usual finite impartial-game Grundy recurrence is not directly valid without additional analysis.

  • grundy_on_dag(g) returns the Grundy number of every vertex in graph g, where g[u] contains all positions reachable from position u in one move.

Implementation

#include <vector>

int grundy_dfs(int u, const std::vector<std::vector<int>> &g, std::vector<int> *memo) {
  if ((*memo)[u] != -1) {
    return (*memo)[u];
  }
  std::vector<bool> seen(g[u].size() + 1, false);
  for (int v : g[u]) {
    int x = grundy_dfs(v, g, memo);
    if (x < static_cast<int>(seen.size())) {
      seen[x] = true;
    }
  }
  int res = 0;
  while (res < static_cast<int>(seen.size()) && seen[res]) {
    res++;
  }
  (*memo)[u] = res;
  return res;
}

std::vector<int> grundy_on_dag(const std::vector<std::vector<int>> &g) {
  std::vector<int> memo(g.size(), -1);
  for (int u = 0; u < static_cast<int>(g.size()); u++) {
    grundy_dfs(u, g, &memo);
  }
  return memo;
}

Example Usage

#include <cassert>
using namespace std;

int main() {
  vector<vector<int>> g{{1, 2}, {3}, {3, 4}, {}, {}};
  auto grundy = grundy_on_dag(g);
  assert(grundy[3] == 0);
  assert(grundy[4] == 0);
  assert(grundy[1] == 1);
  assert(grundy[2] == 1);
  assert(grundy[0] == 0);
  return 0;
}