Problem 781: Feynman Diagrams
View on Project EulerProject Euler Problem 781 Solution
EulerSolve provides an optimized solution for Project Euler Problem 781, Feynman Diagrams, with C++, Python, Java, and a step-by-step mathematical explanation.
Problem Summary Problem 781 asks for a large modular value \(F(n)\) associated with counting Feynman-diagram configurations. The implementations reveal the structural facts that matter computationally: the modulus is \(10^9+7\), odd values of \(n\) contribute nothing, and the first nontrivial checkpoints are $F(4)=5,\qquad F(8)=319.$ Instead of enumerating diagrams directly, the solution compresses the count into a one-dimensional profile that evolves only when \(n\) increases by \(2\). That recurrence is the real mathematical core of the program. Mathematical Approach Write \(n=2m\) for even inputs. The implementation maintains a profile \(A_m(j)\) indexed by a positive integer \(j\). The zeroth slot is unused, so only \(j\ge 1\) matters. For odd inputs, the answer is immediately $F(2m+1)=0.$ Step 1: Define the State on Even Levels The base layer corresponds to \(n=2\), that is \(m=1\). At this level the profile has a single nonzero entry: $A_1(2)=1,\qquad A_1(j)=0\quad (j\ne 2).$ So the positive-index profile is simply $A_1=(0,1).$ Every later even level is produced from the previous one by the same two deterministic transforms. If the current level is \(n=2m\), then the support of the profile lies in \(1\le j\le 2m\). Step 2: Suffix Accumulation First, the current profile is replaced by its suffix sums....
Detailed mathematical approach
Problem Summary
Problem 781 asks for a large modular value \(F(n)\) associated with counting Feynman-diagram configurations. The implementations reveal the structural facts that matter computationally: the modulus is \(10^9+7\), odd values of \(n\) contribute nothing, and the first nontrivial checkpoints are
$F(4)=5,\qquad F(8)=319.$
Instead of enumerating diagrams directly, the solution compresses the count into a one-dimensional profile that evolves only when \(n\) increases by \(2\). That recurrence is the real mathematical core of the program.
Mathematical Approach
Write \(n=2m\) for even inputs. The implementation maintains a profile \(A_m(j)\) indexed by a positive integer \(j\). The zeroth slot is unused, so only \(j\ge 1\) matters. For odd inputs, the answer is immediately
$F(2m+1)=0.$
Step 1: Define the State on Even Levels
The base layer corresponds to \(n=2\), that is \(m=1\). At this level the profile has a single nonzero entry:
$A_1(2)=1,\qquad A_1(j)=0\quad (j\ne 2).$
So the positive-index profile is simply
$A_1=(0,1).$
Every later even level is produced from the previous one by the same two deterministic transforms. If the current level is \(n=2m\), then the support of the profile lies in \(1\le j\le 2m\).
Step 2: Suffix Accumulation
First, the current profile is replaced by its suffix sums. Define
$S_m(j)=\sum_{t=j}^{2m} A_m(t)\qquad (1\le j\le 2m).$
This means each position \(j\) receives the total mass from all positions at least \(j\). In matrix terms this is an upper-triangular transform with ones on and above the diagonal. It is implemented by a single backward scan, which is why the code walks from right to left.
Step 3: Weighted Shift by Two Positions
After the suffix pass, the new layer is extended by two additional indices. Then every old entry \(A_m(j)\) contributes an extra weighted term to position \(j+2\):
$\Delta_{m+1}(j+2)=(j+1)A_m(j).$
So the shift is not uniform; it carries the coefficient \(j+1\). This weighted injection is exactly what makes the recurrence quadratic-time rather than a trivial cumulative sum.
Step 4: Combine the Two Updates into One Recurrence
If we adopt the convention \(A_m(r)=0\) whenever \(r\le 0\) or \(r>2m\), then the two transforms merge into a single formula for the next even layer:
$A_{m+1}(j)=\sum_{t=j}^{2m} A_m(t) + (j-1)A_m(j-2)\qquad (1\le j\le 2m+2).$
The first term is the suffix accumulation, and the second term is the shifted weighted injection rewritten with \(j\) as the destination index. This recurrence is the complete mathematical description of the implemented transition.
Step 5: Recover the Required Value
Once the target even layer has been built, the answer is the sum of the profile entries:
$F(2m)=\sum_{j=1}^{2m} A_m(j).$
There is also a useful consistency identity for the next layer. Summing the recurrence over \(j\) gives
$F(2m+2)=\sum_{j=1}^{2m} jA_m(j)+\sum_{j=1}^{2m}(j+1)A_m(j)=\sum_{j=1}^{2m}(2j+1)A_m(j).$
This is not how the implementation stores the state, but it is a clean way to verify that the transition and the final summation agree.
Worked Example: From \(n=2\) to \(n=8\)
Start from
$A_1=(0,1).$
For \(n=4\), the suffix profile is
$S_1=(1,1),$
and the weighted shift adds \(3\) to the fourth position. Therefore
$A_2=(1,1,0,3),\qquad F(4)=1+1+0+3=5.$
Applying the same rule again gives
$S_2=(5,4,3,3),$
followed by shifted additions \(2,3,0,15\) into positions \(3,4,5,6\). Hence
$A_3=(5,4,5,6,0,15),\qquad F(6)=35.$
One more transition yields
$S_3=(35,30,26,21,15,15),$
and the extra shifted contributions are \(10,12,20,30,0,105\). Thus
$A_4=(35,30,36,33,35,45,0,105),$
so
$F(8)=35+30+36+33+35+45+0+105=319,$
exactly matching the checkpoint embedded in the implementations.
How the Code Works
The C++ implementation stores the current even-layer profile in a dynamic array. If the requested \(n\) is odd, it returns \(0\) immediately. Otherwise it starts from the base layer for \(n=2\), repeats the transition for \(n=4,6,\dots\), and reduces every arithmetic operation modulo \(10^9+7\).
Each transition consists of four practical steps: copy the current profile, run a backward pass to convert that copy into suffix sums, enlarge the storage by two cells, and inject the weighted \((j+1)\)-scaled contributions into positions shifted by \(2\). After the last even layer is reached, the implementation sums the entire profile to obtain \(F(n)\).
The C++, Python, and Java implementations all rely on the same recurrence. The Python and Java versions act as thin wrappers around the compiled C++ computation, then parse the emitted integer. They therefore inherit the same parity rule, the same checkpoints \(F(4)=5\) and \(F(8)=319\), and the same asymptotic cost as the C++ core.
Complexity Analysis
Let \(n=2m\). The profile length grows linearly: at level \(2,4,6,\dots,2m\) it has \(3,5,7,\dots,2m+1\) slots including the unused zeroth slot. A single transition processes the current layer with one backward suffix scan and one forward weighted-shift scan, so each layer costs \(O(m)\) time at its own scale. Summing over all even layers gives
$O(1+2+\cdots+m)=O(m^2)=O(n^2).$
Only the current layer and the next layer are stored, so the memory use is linear:
$O(m)=O(n).$
Footnotes and References
- Problem page: https://projecteuler.net/problem=781
- Feynman diagram: Wikipedia — Feynman diagram
- Dynamic programming: Wikipedia — Dynamic programming
- Recurrence relation: Wikipedia — Recurrence relation
- Modular arithmetic: Wikipedia — Modular arithmetic