Problem 434: Rigid Graphs
View on Project EulerProject Euler Problem 434 Solution
EulerSolve provides an optimized solution for Project Euler Problem 434, Rigid Graphs, with C++, Python, Java, and a step-by-step mathematical explanation.
Problem Summary Consider an \(m\times n\) rectangular grid of unit squares. Each cell may be braced by a diagonal, and \(R(m,n)\) denotes the number of brace selections that make the whole framework rigid. The final target is $S(N)=\sum_{m=1}^{N}\sum_{n=1}^{N} R(m,n)\pmod{1{,}000{,}000{,}033}.$ The implementations do not attack rigidity directly as a geometric problem. Instead, they use the standard combinatorial model in which rigidity becomes a connectivity question in a bipartite graph. Mathematical Approach Step 1: Convert the braced grid into a bipartite graph Create one vertex for each horizontal strip of cells and one vertex for each vertical strip of cells. Thus an \(m\times n\) grid gives a bipartite graph with row vertices \(u_1,\dots,u_m\) and column vertices \(v_1,\dots,v_n\). The cell in row \(a\) and column \(b\) corresponds to the possible edge \(u_a v_b\). Choosing a set of braces is therefore equivalent to choosing a spanning subgraph of the complete bipartite graph \(K_{m,n}\). The key fact used by the solver is: $\text{the braced framework is rigid} \iff \text{the associated bipartite graph is connected}.$ So \(R(m,n)\) is exactly the number of connected spanning subgraphs of \(K_{m,n}\). Step 2: Count all configurations first There are \(mn\) possible row-column edges, and each edge is either present or absent....
Detailed mathematical approach
Problem Summary
Consider an \(m\times n\) rectangular grid of unit squares. Each cell may be braced by a diagonal, and \(R(m,n)\) denotes the number of brace selections that make the whole framework rigid. The final target is
$S(N)=\sum_{m=1}^{N}\sum_{n=1}^{N} R(m,n)\pmod{1{,}000{,}000{,}033}.$
The implementations do not attack rigidity directly as a geometric problem. Instead, they use the standard combinatorial model in which rigidity becomes a connectivity question in a bipartite graph.
Mathematical Approach
Step 1: Convert the braced grid into a bipartite graph
Create one vertex for each horizontal strip of cells and one vertex for each vertical strip of cells. Thus an \(m\times n\) grid gives a bipartite graph with row vertices \(u_1,\dots,u_m\) and column vertices \(v_1,\dots,v_n\).
The cell in row \(a\) and column \(b\) corresponds to the possible edge \(u_a v_b\). Choosing a set of braces is therefore equivalent to choosing a spanning subgraph of the complete bipartite graph \(K_{m,n}\).
The key fact used by the solver is:
$\text{the braced framework is rigid} \iff \text{the associated bipartite graph is connected}.$
So \(R(m,n)\) is exactly the number of connected spanning subgraphs of \(K_{m,n}\).
Step 2: Count all configurations first
There are \(mn\) possible row-column edges, and each edge is either present or absent. Therefore the total number of subgraphs is
$2^{mn}.$
To get \(R(m,n)\), we subtract the disconnected subgraphs.
Step 3: Classify a disconnected graph by the component of one fixed row vertex
Fix the distinguished row vertex \(u_1\). In a disconnected graph, the connected component containing \(u_1\) uses some number \(i\) of row vertices and some number \(j\) of column vertices, where
$1\le i\le m,\qquad 0\le j\le n,\qquad (i,j)\ne(m,n).$
For such a component:
$\binom{m-1}{i-1}$
chooses the other row vertices that join \(u_1\),
$\binom{n}{j}$
chooses the participating column vertices,
$R(i,j)$
counts the connected bipartite graph inside that component, and
$2^{(m-i)(n-j)}$
counts the arbitrary edges among the remaining rows and remaining columns. No edge is allowed between the chosen component and the remaining vertices, otherwise the component containing \(u_1\) would be larger.
Summing this contribution over all proper pairs \((i,j)\) gives the total number of disconnected graphs.
Step 4: The recurrence used by the implementations
Subtracting those disconnected cases from \(2^{mn}\) yields the split recurrence actually evaluated by the implementations, with \(M=1{,}000{,}000{,}033\):
$R(m,n)=2^{mn}-\sum_{i=1}^{m-1}\binom{m-1}{i-1}\sum_{j=0}^{n}\binom{n}{j}R(i,j)2^{(m-i)(n-j)}-\sum_{j=0}^{n-1}\binom{n}{j}R(m,j)\pmod{M}.$
The second sum is simply the case \(i=m\), where the factor \(2^{(m-i)(n-j)}\) becomes \(1\).
The boundary values are also important. A graph with one row vertex and no column vertices is connected, so
$R(1,0)=1.$
For \(m>1\), a graph with no column vertices cannot be connected, so \(R(m,0)=0\). From the recurrence one also gets \(R(1,n)=1\) for every \(n\ge 1\): with only one row vertex, all \(n\) column vertices must attach to it.
Step 5: Small worked examples
For \(R(2,2)\), start from all \(2^4=16\) subgraphs. The component containing the first row can have size \((1,0)\), \((1,1)\), or \((1,2)\), giving
$\binom{1}{0}\left[\binom{2}{0}R(1,0)2^2+\binom{2}{1}R(1,1)2^1+\binom{2}{2}R(1,2)2^0\right]=4+4+1=9.$
The case \(i=2\) but \(j<2\) contributes
$\binom{2}{0}R(2,0)+\binom{2}{1}R(2,1)=0+2=2.$
Therefore
$R(2,2)=16-9-2=5.$
For \(R(2,3)\), the same recurrence gives
$\binom{1}{0}\left[\binom{3}{0}R(1,0)2^3+\binom{3}{1}R(1,1)2^2+\binom{3}{2}R(1,2)2^1+\binom{3}{3}R(1,3)2^0\right]=8+12+6+1=27,$
and the \(i=2,\ j<3\) part equals
$\binom{3}{0}R(2,0)+\binom{3}{1}R(2,1)+\binom{3}{2}R(2,2)=0+3+15=18.$
Hence
$R(2,3)=2^6-27-18=64-45=19.$
These small values are useful sanity checks for the table.
How the Code Works
The C++, Python, and Java implementations precompute two ingredients modulo \(M\): all binomial coefficients \(\binom{a}{b}\) for \(0\le a,b\le 100\), and all powers \(2^e\) for \(0\le e\le 100^2\). After that they fill a table of \(R(m,n)\) in increasing order of \(m\) and \(n\).
For a fixed \(m\), the contribution of all terms with smaller row count \(i<m\) is accumulated first for every \(n\). Then, for each \(n\), the contribution with the same row count but smaller column count \(j<n\) is subtracted. What remains is exactly \(R(m,n)\).
The C++ implementation optionally parallelizes the accumulation over the smaller row counts, while the Python and Java implementations evaluate the same recurrence directly with ordinary nested loops. Once the table is complete, the final answer is the double sum of all \(R(m,n)\) with \(1\le m,n\le N\).
Complexity Analysis
Let the final bound be \(N\). Building Pascal's triangle and the powers of two each costs \(O(N^2)\) time. The dynamic-programming table is dominated by the triple summation over \(m\), the smaller row size \(i\), and the inner column index \(j\), which yields overall \(O(N^4)\) time. The table of values, binomial coefficients, and power table together use \(O(N^2)\) memory.
For the required case \(N=100\), this is entirely practical.
Footnotes and References
- Problem page: https://projecteuler.net/problem=434
- Bipartite graph: Wikipedia - Bipartite graph
- Connected graph: Wikipedia - Connectivity (graph theory)
- Binomial coefficient: Wikipedia - Binomial coefficient