Row reduction solves , computes rank, inverse, null space and solution counts in ; over the rows are bitsets and the cost drops by 64. mint from Modular Arithmetic; determinants are in Determinants.

VariantWhat it gives
Gaussian elimination (row echelon form) + back substitutionsolution of ; determinant = product of pivots, sign flipped per swap
Gauss–Jordan (reduced row echelon form)solution read off directly; inverse from ; several right-hand sides at once; pivot and free columns expose a null-space basis
LU decompositionrecords the multipliers: , then each new costs
ranknumber of pivots
over exact; solution count
over XOR systems with bitset rows, ; the XOR basis is the same computation
over doublespartial pivoting (largest in the column) for stability, compare with
simplexGauss–Jordan pivots generalised to inequalities (Linear Programming)
Condition after reducing ( unknowns)Solutions
(a row )none
unique
free variables: infinitely many over , exactly over (usually what the problem asks)
int gaussDouble(vector<vector<double>> a, vector<double>& x) {   // a: n x (m+1) augmented; 0 none, 1 unique, 2 infinitely many
    const double EPS = 1e-9; int n = sz(a), m = sz(a[0]) - 1; vector<int> where(m, -1);
    for (int col = 0, row = 0; col < m && row < n; col++) {
        int sel = row; for (int i = row; i < n; i++) if (fabs(a[i][col]) > fabs(a[sel][col])) sel = i;
        if (fabs(a[sel][col]) < EPS) continue;
        swap(a[sel], a[row]); where[col] = row;
        for (int i = 0; i < n; i++) if (i != row) { double f = a[i][col] / a[row][col]; for (int j = col; j <= m; j++) a[i][j] -= a[row][j] * f; }
        row++;
    }
    x.assign(m, 0);
    for (int j = 0; j < m; j++) if (where[j] != -1) x[j] = a[where[j]][m] / a[where[j]][j];
    for (int i = 0; i < n; i++) { double s = 0; for (int j = 0; j < m; j++) s += x[j] * a[i][j]; if (fabs(s - a[i][m]) > EPS) return 0; }
    for (int j = 0; j < m; j++) if (where[j] == -1) return 2;
    return 1;
}
 
struct LinearSolution { bool ok = false; vector<mint> x; vector<vector<mint>> kernel; int rank = 0; };   // all solutions: x + span(kernel)
LinearSolution solveMod(vector<vector<mint>> a, vector<mint> b) {   // n equations, m unknowns, prime modulus
    int n = sz(a), m = n ? sz(a[0]) : 0; vector<int> where(m, -1); int row = 0;
    for (int i = 0; i < n; i++) a[i].push_back(b[i]);
    for (int col = 0; col < m && row < n; col++) {
        int sel = -1; for (int i = row; i < n; i++) if (a[i][col] != mint(0)) { sel = i; break; }
        if (sel < 0) continue;
        swap(a[sel], a[row]); where[col] = row;
        mint inv = a[row][col].inv(); for (int j = col; j <= m; j++) a[row][j] *= inv;
        for (int i = 0; i < n; i++) if (i != row && a[i][col] != mint(0)) { mint f = a[i][col]; for (int j = col; j <= m; j++) a[i][j] -= f * a[row][j]; }
        row++;
    }
    LinearSolution res; res.rank = row;
    for (int i = row; i < n; i++) if (a[i][m] != mint(0)) return res;   // 0 = c
    res.ok = true; res.x.assign(m, 0);
    for (int j = 0; j < m; j++) if (where[j] != -1) res.x[j] = a[where[j]][m];
    for (int f = 0; f < m; f++) if (where[f] == -1) {      // one kernel vector per free column
        vector<mint> v(m, 0); v[f] = 1;
        for (int j = 0; j < m; j++) if (where[j] != -1) v[j] = -a[where[j]][f];
        res.kernel.push_back(v);
    }
    return res;
}
 
template <class T> vector<vector<T>> matrixInverse(vector<vector<T>> a) {   // Gauss-Jordan on [A | I]; {} if singular
    int n = sz(a);
    for (int i = 0; i < n; i++) a[i].resize(2 * n), a[i][n + i] = T(1);
    for (int c = 0; c < n; c++) {
        int sel = c;
        for (int i = c + 1; i < n; i++) {
            if constexpr (is_floating_point_v<T>) { if (abs(a[i][c]) > abs(a[sel][c])) sel = i; }
            else { if (a[sel][c] == T(0)) sel = i; }
        }
        if constexpr (is_floating_point_v<T>) { if (abs(a[sel][c]) < 1e-9) return {}; }
        else { if (a[sel][c] == T(0)) return {}; }
        swap(a[sel], a[c]);
        T inv = T(1) / a[c][c]; for (int j = c; j < 2 * n; j++) a[c][j] *= inv;
        for (int i = 0; i < n; i++) if (i != c && a[i][c] != T(0)) { T f = a[i][c]; for (int j = c; j < 2 * n; j++) a[i][j] -= f * a[c][j]; }
    }
    for (auto& row : a) row.erase(row.begin(), row.begin() + n);
    return a;
}

Rank and its uses

ProblemRank answer
distinct subset XORs of numbers
subsets with a given XOR or 0
XOR equations (light switches, Lights Out) elimination
perfect matching exists (general graph)Tutte matrix with random values has rank (General Matching)
maximum matching size rank of the random Tutte matrix (Lovász)
dimension of a span, independence of vectorsrank (= number of vectors iff independent)
solutions of a modular linear system
minimum generators of -subgrouprank

Numerical rank. Over the reals “is this entry zero” is meaningless in floating point: use the singular value decomposition and count singular values above a tolerance. In contests, work over instead (exact). Sparse systems: elimination causes fill-in; use Wiedemann or Lanczos, which only need matrix–vector products, time and extra space.

Over GF(2)

int rankF2(vector<ull> a) {                                // rows as 64-bit masks
    int n = sz(a), rk = 0;
    for (int col = 63; col >= 0 && rk < n; col--) {
        int piv = -1; for (int r = rk; r < n; r++) if (a[r] >> col & 1) { piv = r; break; }
        if (piv < 0) continue;
        swap(a[rk], a[piv]);
        for (int r = 0; r < n; r++) if (r != rk && (a[r] >> col & 1)) a[r] ^= a[rk];
        rk++;
    }
    return rk;
}
 
template <int N> int solveXor(vector<bitset<N>> a, int m, bitset<N>& x) {   // column m holds b; returns -1 or #free vars
    int n = sz(a), row = 0; vector<int> where(m, -1);
    for (int col = 0; col < m && row < n; col++) {
        int piv = -1; for (int i = row; i < n; i++) if (a[i][col]) { piv = i; break; }
        if (piv < 0) continue;
        swap(a[piv], a[row]); where[col] = row;
        for (int i = 0; i < n; i++) if (i != row && a[i][col]) a[i] ^= a[row];
        row++;
    }
    for (int i = row; i < n; i++) if (a[i][m]) return -1;
    x.reset(); for (int j = 0; j < m; j++) if (where[j] != -1) x[j] = a[where[j]][m];
    return m - row;
}
 
template <int N> vector<bitset<N>> inverseMod2(vector<bitset<N>> a, int n) {   // {} if singular, O(n^3/64)
    vector<bitset<N>> inv(n); for (int i = 0; i < n; i++) inv[i][i] = 1;
    for (int i = 0; i < n; i++) {
        int piv = i; while (piv < n && !a[piv][i]) piv++;
        if (piv == n) return {};
        swap(a[i], a[piv]), swap(inv[i], inv[piv]);
        for (int j = 0; j < n; j++) if (j != i && a[j][i]) a[j] ^= a[i], inv[j] ^= inv[i];
    }
    return inv;
}
 
template <int N> int rankMod2(vector<bitset<N>> a, int m) {
    int n = sz(a), rank = 0;
    for (int col = 0; col < m && rank < n; col++) {
        int piv = rank; while (piv < n && !a[piv][col]) piv++;
        if (piv == n) continue;
        swap(a[rank], a[piv]);
        for (int j = rank + 1; j < n; j++) if (a[j][col]) a[j] ^= a[rank];
        rank++;
    }
    return rank;
}
 
int lightsOut(int R, int C, const vector<string>& g) {     // min presses to turn all '1' off (press toggles cell + 4 neighbours), -1 if impossible
    const int N = 401; int n = R * C; vector<bitset<N>> a(n);
    for (int r = 0; r < R; r++) for (int c = 0; c < C; c++) {
        int i = r * C + c; a[i][n] = g[r][c] == '1';
        int dr[] = {0, 1, -1, 0, 0}, dc[] = {0, 0, 0, 1, -1};
        for (int d = 0; d < 5; d++) { int rr = r + dr[d], cc = c + dc[d]; if (rr >= 0 && rr < R && cc >= 0 && cc < C) a[i][rr * C + cc] = 1; }
    }
    int row = 0; vector<int> where(n, -1);                 // reduce, then enumerate the free variables (2^free)
    for (int col = 0; col < n && row < n; col++) {
        int piv = -1; for (int i = row; i < n; i++) if (a[i][col]) { piv = i; break; }
        if (piv < 0) continue;
        swap(a[piv], a[row]); where[col] = row;
        for (int i = 0; i < n; i++) if (i != row && a[i][col]) a[i] ^= a[row];
        row++;
    }
    for (int i = row; i < n; i++) if (a[i][n]) return -1;
    vector<int> fr; for (int j = 0; j < n; j++) if (where[j] == -1) fr.push_back(j);
    int best = INT_MAX;
    for (int mask = 0; mask < (1 << sz(fr)); mask++) {
        bitset<N> x; for (int t = 0; t < sz(fr); t++) x[fr[t]] = mask >> t & 1;
        for (int j = 0; j < n; j++) if (where[j] != -1) { bitset<N> rowv = a[where[j]]; rowv[n] = 0; rowv[j] = 0; x[j] = a[where[j]][n] ^ ((rowv & x).count() & 1); }
        best = min(best, (int)x.count());
    }
    return best;
}

Lights Out. Each cell is an variable (pressed or not; order and repetition don’t matter), each light gives one equation. Rank : unique solution; rank : solutions, enumerate the free variables for the fewest presses; inconsistent: impossible. This models the whole family of toggle puzzles. GF(2) determinant and matchings. , so of a bipartite adjacency matrix mod 2 is the parity of the number of perfect matchings; CF 736D: if the count is odd, removing edge keeps it odd iff inv[v][u] == 0.