Problem 729: Range of Periodic Sequence
View on Project EulerProject Euler Problem 729 Solution
EulerSolve provides an optimized solution for Project Euler Problem 729, Range of Periodic Sequence, with C++, Python, Java, and a step-by-step mathematical explanation.
Problem Summary Let $$T(x)=x-\frac{1}{x},\qquad x\neq 0.$$ For each exact period \(m\) with \(2\le m\le p\), the problem considers real periodic sequences generated by repeated application of \(T\). If one period of the sequence is \((x_0,x_1,\dots,x_{m-1})\), its range is $$\max_{0\le i\lt m} x_i-\min_{0\le i\lt m} x_i.$$ The target quantity \(S(p)\) is the sum of these ranges over all primitive periodic sequences of lengths up to \(p\), counting different cyclic starting positions separately. Directly searching real periodic sequences is not practical, so the implementation re-encodes each orbit by inverse branches and solves one fixed-point problem per primitive orbit. Mathematical Approach The solver converts the periodic-sequence problem into a combinatorial enumeration of primitive binary necklaces, followed by a numerical fixed-point solve for each necklace. Step 1: Write the Two Inverse Branches If \(y=T(x)\), then \(x\) satisfies $$x^2-yx-1=0.$$ So every value \(y\) has two real preimages, namely $$g_{\pm}(y)=\frac{y\pm\sqrt{y^2+4}}{2}.$$ A periodic orbit can therefore be reconstructed backward by choosing, at each step, either the \(+\) branch or the \(-\) branch....
Detailed mathematical approach
Problem Summary
Let
$$T(x)=x-\frac{1}{x},\qquad x\neq 0.$$
For each exact period \(m\) with \(2\le m\le p\), the problem considers real periodic sequences generated by repeated application of \(T\). If one period of the sequence is \((x_0,x_1,\dots,x_{m-1})\), its range is
$$\max_{0\le i\lt m} x_i-\min_{0\le i\lt m} x_i.$$
The target quantity \(S(p)\) is the sum of these ranges over all primitive periodic sequences of lengths up to \(p\), counting different cyclic starting positions separately. Directly searching real periodic sequences is not practical, so the implementation re-encodes each orbit by inverse branches and solves one fixed-point problem per primitive orbit.
Mathematical Approach
The solver converts the periodic-sequence problem into a combinatorial enumeration of primitive binary necklaces, followed by a numerical fixed-point solve for each necklace.
Step 1: Write the Two Inverse Branches
If \(y=T(x)\), then \(x\) satisfies
$$x^2-yx-1=0.$$
So every value \(y\) has two real preimages, namely
$$g_{\pm}(y)=\frac{y\pm\sqrt{y^2+4}}{2}.$$
A periodic orbit can therefore be reconstructed backward by choosing, at each step, either the \(+\) branch or the \(-\) branch. A binary word \(\sigma=(s_0,s_1,\dots,s_{m-1})\) with \(s_i\in\{-1,+1\}\) determines the composition
$$G_\sigma=g_{s_{m-1}}\circ g_{s_{m-2}}\circ \cdots \circ g_{s_0}.$$
A period-\(m\) orbit appears when some starting value \(x_0\) satisfies the fixed-point equation
$$x_0=G_\sigma(x_0).$$
Step 2: Why Primitive Necklaces Are the Right Objects
Two binary words that differ only by a cyclic rotation describe the same orbit, just started at different points of the cycle. So the natural combinatorial object is not a raw binary word but a binary necklace.
If a word is a repetition of a shorter block, then the corresponding orbit has a smaller fundamental period. Therefore exact period \(m\) is represented by primitive binary necklaces of length \(m\). Their number is
$$P_m=\frac{1}{m}\sum_{d\mid m}\mu(d)\,2^{m/d},$$
where \(\mu\) is the Möbius function. This count is also used as a structural check before the numerical stage begins.
Step 3: Show That Each Branch Word Has a Unique Real Fixed Point
For \(s\in\{-1,+1\}\), differentiating one inverse branch gives
$$g_s'(y)=\frac{1}{2}\left(1+s\frac{y}{\sqrt{y^2+4}}\right).$$
Because
$$-1\lt\frac{y}{\sqrt{y^2+4}}\lt 1,$$
we obtain
$$0\lt g_s'(y)\lt 1.$$
So each inverse branch is an increasing contraction on \(\mathbb{R}\), and any finite composition \(G_\sigma\) is again a contraction. By the contraction mapping principle, every branch word \(\sigma\) has a unique real fixed point. That is why a fixed-point iteration converges reliably and why each primitive necklace corresponds to exactly one real primitive orbit.
Step 4: Recover the Orbit and Its Range
Once the fixed point \(x_0\) of \(G_\sigma\) is known, the orbit itself is obtained by applying the inverse branches in the chosen order:
$$x_{i+1}=g_{s_i}(x_i)\qquad (0\le i\le m-2).$$
The final branch returns from \(x_{m-1}\) to \(x_0\), closing the cycle. The range attached to \(\sigma\) is
$$R(\sigma)=\max_{0\le i\lt m} x_i-\min_{0\le i\lt m} x_i.$$
A primitive necklace represents one orbit up to rotation, but the original sum counts all \(m\) cyclic starting positions as distinct periodic sequences. Therefore one primitive necklace contributes
$$m\,R(\sigma),$$
and the full target becomes
$$S(p)=\sum_{m=2}^{p}\ \sum_{\sigma\in\mathcal{N}_m^{\mathrm{prim}}} m\,R(\sigma),$$
where \(\mathcal{N}_m^{\mathrm{prim}}\) denotes the primitive binary necklaces of length \(m\).
Step 5: Worked Example for \(m=2\)
There is exactly one primitive binary necklace of length \(2\): one \(+\) branch and one \(-\) branch, up to rotation. Its orbit is a 2-cycle \(\{x,-x\}\). Indeed, the condition \(T(x)=-x\) gives
$$x-\frac{1}{x}=-x,$$
so
$$2x=\frac{1}{x},\qquad x^2=\frac{1}{2}.$$
Thus the orbit points are
$$x=\pm\frac{1}{\sqrt{2}}.$$
The range is therefore
$$\frac{1}{\sqrt{2}}-\left(-\frac{1}{\sqrt{2}}\right)=\sqrt{2},$$
and because there are two cyclic starting positions, the total contribution is
$$2\sqrt{2}=2.8284271247\ldots$$
This matches the smallest validation value used by the implementation.
How the Code Works
The C++, Python, and Java implementations follow the same mathematical pipeline. For each \(m=2,3,\dots,p\), they generate all primitive binary necklaces of length \(m\) using a recursive necklace generator. Before any real-valued computation is performed, the generated count is compared with the Möbius-inversion formula above to confirm that only primitive representatives are present.
For each representative, the implementation starts from \(0\) and repeatedly applies the full inverse-branch composition \(G_\sigma\). This provides a stable initial approximation to the fixed point. It then refines that approximation with Newton's method applied to
$$\Phi(x)=G_\sigma(x)-x.$$
During this refinement, the derivative of the composition is accumulated by the chain rule while traversing the chosen branches, so the Newton correction uses the exact derivative of the composed inverse map.
After the fixed point is obtained, the implementation unfolds the orbit one branch at a time, tracks the minimum and maximum values encountered, computes \(R(\sigma)\), and adds \(mR(\sigma)\) to the running total. Compensated summation is used to reduce floating-point cancellation. The C++ and Python implementations distribute independent necklace blocks across workers, while the Java implementation delegates the numerical evaluation to the same underlying computation and returns the resulting decimal value.
Complexity Analysis
Let
$$P_m=\frac{1}{m}\sum_{d\mid m}\mu(d)\,2^{m/d}$$
be the number of primitive binary necklaces of length \(m\). Processing one necklace requires \(O(mI)\) arithmetic operations, where \(I\) is the small number of fixed-point and Newton iterations. Hence the total running time is
$$O\!\left(\sum_{m=2}^{p} P_m\,m\,I\right).$$
Since \(P_m\sim 2^m/m\), the growth is essentially exponential in \(p\), with the largest periods dominating the cost. Memory usage for a single length is \(O(P_m)\) to store the necklace representatives, plus \(O(1)\) orbit data and a small amount of worker-local accumulation state.
Footnotes and References
- Problem page: Project Euler 729
- Necklace counting: Wikipedia - Necklace (combinatorics)
- Möbius inversion: Wikipedia - Möbius inversion formula
- Contraction mapping principle: Wikipedia - Banach fixed-point theorem
- Newton's method: Wikipedia - Newton's method
- Compensated summation: Wikipedia - Kahan summation algorithm
Problem 729 source code
C++
#include <algorithm>
#include <atomic>
#include <cmath>
#include <cstdint>
#include <functional>
#include <iomanip>
#include <iostream>
#include <limits>
#include <string>
#include <thread>
#include <vector>
namespace {
using u32 = std::uint32_t;
using i64 = std::int64_t;
struct Options {
int p = 25;
bool run_checkpoints = true;
bool allow_multithreading = true;
unsigned requested_threads = 0U;
};
struct KahanSum {
long double sum = 0.0L;
long double c = 0.0L;
void add(const long double x) {
const long double y = x - c;
const long double t = sum + y;
c = (t - sum) - y;
sum = t;
}
};
bool parse_u32_after_prefix(const std::string& arg, const char* prefix, u32& value) {
const std::string p(prefix);
if (arg.rfind(p, 0) != 0U) {
return false;
}
const std::string tail = arg.substr(p.size());
if (tail.empty()) {
return false;
}
std::uint64_t parsed = 0ULL;
for (const char c : tail) {
if (c < '0' || c > '9') {
return false;
}
parsed = parsed * 10ULL + static_cast<std::uint64_t>(c - '0');
if (parsed > static_cast<std::uint64_t>(std::numeric_limits<u32>::max())) {
return false;
}
}
value = static_cast<u32>(parsed);
return true;
}
bool parse_unsigned_after_prefix(const std::string& arg,
const char* prefix,
unsigned& value) {
u32 parsed = 0U;
if (!parse_u32_after_prefix(arg, prefix, parsed)) {
return false;
}
value = static_cast<unsigned>(parsed);
return true;
}
bool parse_arguments(const int argc, char** argv, Options& options) {
for (int i = 1; i < argc; ++i) {
const std::string arg(argv[i]);
if (arg == "--skip-checkpoints") {
options.run_checkpoints = false;
continue;
}
if (arg == "--single-thread") {
options.allow_multithreading = false;
continue;
}
u32 parsed_u32 = 0U;
if (parse_u32_after_prefix(arg, "--p=", parsed_u32)) {
if (parsed_u32 > static_cast<u32>(std::numeric_limits<int>::max())) {
std::cerr << "--p is too large.\n";
return false;
}
options.p = static_cast<int>(parsed_u32);
continue;
}
unsigned parsed_unsigned = 0U;
if (parse_unsigned_after_prefix(arg, "--threads=", parsed_unsigned)) {
options.requested_threads = parsed_unsigned;
continue;
}
std::cerr << "Unknown argument: " << arg << '\n';
return false;
}
if (options.p < 2) {
std::cerr << "--p must be at least 2.\n";
return false;
}
return true;
}
unsigned choose_thread_count(const bool allow_multithreading,
const unsigned requested_threads,
const std::size_t workload) {
if (!allow_multithreading || workload < 2048ULL) {
return 1U;
}
unsigned threads = requested_threads;
if (threads == 0U) {
threads = std::thread::hardware_concurrency();
if (threads == 0U) {
threads = 1U;
}
}
return std::max(1U, std::min<unsigned>(threads, static_cast<unsigned>(workload)));
}
long double apply_inverse_word(long double y, const u32 bits, const int m) {
for (int i = 0; i < m; ++i) {
const long double d = std::sqrt(y * y + 4.0L);
if (((bits >> i) & 1U) != 0U) {
y = 0.5L * (y + d);
} else {
y = 0.5L * (y - d);
}
}
return y;
}
long double solve_fixed_point(const u32 bits, const int m) {
long double y = 0.0L;
for (int it = 0; it < 80; ++it) {
const long double ny = apply_inverse_word(y, bits, m);
if (std::fabsl(ny - y) < 1e-28L) {
return ny;
}
y = ny;
}
for (int it = 0; it < 16; ++it) {
long double v = y;
long double deriv = 1.0L;
for (int i = 0; i < m; ++i) {
const long double d = std::sqrt(v * v + 4.0L);
const int s = (((bits >> i) & 1U) != 0U) ? 1 : -1;
deriv *= 0.5L * (1.0L + static_cast<long double>(s) * (v / d));
v = 0.5L * (v + static_cast<long double>(s) * d);
}
const long double phi = v - y;
const long double dphi = deriv - 1.0L;
if (std::fabsl(dphi) < 1e-30L) {
break;
}
const long double step = phi / dphi;
y -= step;
if (std::fabsl(step) < 1e-28L) {
break;
}
}
return y;
}
long double orbit_range_from_inverse_word(const long double x0, const u32 bits, const int m) {
long double mn = x0;
long double mx = x0;
long double y = x0;
// The fixed point x0 of g_{s_{m-1}} ... g_{s_0} generates the same orbit
// by repeatedly applying g_{s_0}, g_{s_1}, ..., g_{s_{m-2}}.
for (int i = 0; i < m - 1; ++i) {
const long double d = std::sqrt(y * y + 4.0L);
if (((bits >> i) & 1U) != 0U) {
y = 0.5L * (y + d);
} else {
y = 0.5L * (y - d);
}
mn = std::min(mn, y);
mx = std::max(mx, y);
}
return mx - mn;
}
int mobius(const int n) {
int x = n;
int prime_count = 0;
for (int p = 2; p * p <= x; ++p) {
if (x % p != 0) {
continue;
}
int exp = 0;
while (x % p == 0) {
x /= p;
++exp;
}
if (exp > 1) {
return 0;
}
++prime_count;
}
if (x > 1) {
++prime_count;
}
return (prime_count % 2 == 0) ? 1 : -1;
}
i64 expected_primitive_necklaces(const int n) {
i64 sum = 0;
for (int d = 1; d <= n; ++d) {
if (n % d != 0) {
continue;
}
const int mu = mobius(d);
if (mu == 0) {
continue;
}
sum += static_cast<i64>(mu) * (1LL << (n / d));
}
return sum / n;
}
void generate_primitive_necklaces(const int n, std::vector<u32>& out) {
std::vector<int> a(static_cast<std::size_t>(n) + 1ULL, 0);
out.clear();
out.reserve(static_cast<std::size_t>(expected_primitive_necklaces(n)));
std::function<void(int, int)> gen = [&](const int t, const int p) {
if (t > n) {
if (n % p == 0 && p == n) {
u32 bits = 0U;
for (int i = 1; i <= n; ++i) {
bits |= static_cast<u32>(a[i]) << static_cast<u32>(i - 1);
}
out.push_back(bits);
}
return;
}
a[t] = a[t - p];
gen(t + 1, p);
for (int j = a[t - p] + 1; j < 2; ++j) {
a[t] = j;
gen(t + 1, t);
}
};
gen(1, 1);
}
long double solve_for_p(const int p,
const bool allow_multithreading,
const unsigned requested_threads,
const bool validate_counts) {
KahanSum total;
for (int m = 2; m <= p; ++m) {
std::vector<u32> necklaces;
generate_primitive_necklaces(m, necklaces);
if (validate_counts) {
const i64 expected = expected_primitive_necklaces(m);
if (static_cast<i64>(necklaces.size()) != expected) {
std::cerr << "Necklace count mismatch at m=" << m << ": expected " << expected
<< ", got " << necklaces.size() << "\n";
std::exit(1);
}
}
const unsigned threads =
choose_thread_count(allow_multithreading, requested_threads, necklaces.size());
if (threads == 1U) {
for (const u32 bits : necklaces) {
const long double x0 = solve_fixed_point(bits, m);
const long double r = orbit_range_from_inverse_word(x0, bits, m);
total.add(static_cast<long double>(m) * r);
}
continue;
}
std::vector<KahanSum> partials(threads);
auto worker = [&](const unsigned tid) {
const std::size_t begin = (necklaces.size() * tid) / threads;
const std::size_t end = (necklaces.size() * (tid + 1U)) / threads;
for (std::size_t idx = begin; idx < end; ++idx) {
const u32 bits = necklaces[idx];
const long double x0 = solve_fixed_point(bits, m);
const long double r = orbit_range_from_inverse_word(x0, bits, m);
partials[tid].add(static_cast<long double>(m) * r);
}
};
std::vector<std::thread> pool;
pool.reserve(threads);
for (unsigned t = 0U; t < threads; ++t) {
pool.emplace_back(worker, t);
}
for (auto& th : pool) {
th.join();
}
for (const auto& ps : partials) {
total.add(ps.sum);
total.add(ps.c);
}
}
return total.sum;
}
bool run_checkpoints(const bool allow_multithreading, const unsigned requested_threads) {
struct Checkpoint {
int p;
long double expected;
};
const std::vector<Checkpoint> checks = {
{2, 2.8284271247461900976L},
{3, 14.646120160892684L},
{5, 124.10555788745795L},
};
for (const auto& cp : checks) {
const long double got = solve_for_p(cp.p, allow_multithreading, requested_threads, true);
const long double err = std::fabsl(got - cp.expected);
if (err > 5e-10L) {
std::cerr << std::setprecision(18)
<< "Checkpoint failed for P=" << cp.p << ": expected " << cp.expected
<< ", got " << got << ", |err|=" << err << "\n";
return false;
}
}
std::cerr << "Validation checkpoints passed.\n";
return true;
}
} // namespace
int main(int argc, char** argv) {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
Options options;
if (!parse_arguments(argc, argv, options)) {
return 1;
}
if (options.run_checkpoints &&
!run_checkpoints(options.allow_multithreading, options.requested_threads)) {
return 1;
}
const long double ans =
solve_for_p(options.p, options.allow_multithreading, options.requested_threads, false);
std::cout << std::fixed << std::setprecision(10) << ans << '\n';
return 0;
}
Python
import math
import multiprocessing
class KahanSum:
def __init__(self):
self.sum = 0.0
self.c = 0.0
def add(self, x):
y = x - self.c
t = self.sum + y
self.c = (t - self.sum) - y
self.sum = t
def apply_inverse_word(y, bits, m):
for i in range(m):
d = math.sqrt(y * y + 4.0)
if (bits >> i) & 1:
y = 0.5 * (y + d)
else:
y = 0.5 * (y - d)
return y
def solve_fixed_point(bits, m):
y = 0.0
for it in range(80):
ny = apply_inverse_word(y, bits, m)
if abs(ny - y) < 1e-15:
return ny
y = ny
for it in range(16):
v = y
deriv = 1.0
for i in range(m):
d = math.sqrt(v * v + 4.0)
s = 1 if (bits >> i) & 1 else -1
deriv *= 0.5 * (1.0 + s * (v / d))
v = 0.5 * (v + s * d)
phi = v - y
dphi = deriv - 1.0
if abs(dphi) < 1e-15:
break
step = phi / dphi
y -= step
if abs(step) < 1e-15:
break
return y
def orbit_range_from_inverse_word(x0, bits, m):
mn = x0
mx = x0
y = x0
for i in range(m - 1):
d = math.sqrt(y * y + 4.0)
if (bits >> i) & 1:
y = 0.5 * (y + d)
else:
y = 0.5 * (y - d)
if y < mn: mn = y
if y > mx: mx = y
return mx - mn
def mobius(n):
x = n
prime_count = 0
p = 2
while p * p <= x:
if x % p == 0:
exp = 0
while x % p == 0:
x //= p
exp += 1
if exp > 1: return 0
prime_count += 1
p += 1
if x > 1:
prime_count += 1
return 1 if prime_count % 2 == 0 else -1
def expected_primitive_necklaces(n):
total = 0
for d in range(1, n + 1):
if n % d == 0:
mu = mobius(d)
if mu == 0: continue
total += mu * (1 << (n // d))
return total // n
def generate_primitive_necklaces(n):
out = []
a = [0] * (n + 1)
def gen(t, p):
if t > n:
if n % p == 0 and p == n:
bits = 0
for i in range(1, n + 1):
bits |= (a[i] << (i - 1))
out.append(bits)
return
a[t] = a[t - p]
gen(t + 1, p)
for j in range(a[t - p] + 1, 2):
a[t] = j
gen(t + 1, t)
gen(1, 1)
return out
def worker(m, necklaces):
total = KahanSum()
for bits in necklaces:
x0 = solve_fixed_point(bits, m)
r = orbit_range_from_inverse_word(x0, bits, m)
total.add(m * r)
return total.sum
def solve():
p = 25
total = KahanSum()
threads = max(1, multiprocessing.cpu_count())
pool = multiprocessing.Pool(threads)
for m in range(2, p + 1):
necklaces = generate_primitive_necklaces(m)
chunk_size = max(1, len(necklaces) // threads)
chunks = [necklaces[i:i + chunk_size] for i in range(0, len(necklaces), chunk_size)]
results = pool.starmap(worker, [(m, chunk) for chunk in chunks])
for val in results:
total.add(val)
pool.close()
return f"{total.sum:.10f}"
if __name__ == "__main__":
print(solve())
Java
import java.nio.file.*;
import java.util.*;
import java.util.regex.*;
public class Euler729 {
private static final Pattern ANSWER_RE = Pattern.compile("answer\\s*:\\s*(.+)$", Pattern.CASE_INSENSITIVE);
private static final Pattern EQUAL_RE = Pattern.compile("=\\s*(.+)$");
private static String parseOutput(String stdout) {
String[] lines = stdout.split("\\R");
List<String> nonEmpty = new ArrayList<>();
for (String line : lines) {
String t = line.trim();
if (!t.isEmpty()) {
nonEmpty.add(t);
}
}
if (nonEmpty.isEmpty()) {
return "";
}
List<String> answers = new ArrayList<>();
List<String> equals = new ArrayList<>();
for (String line : nonEmpty) {
Matcher m1 = ANSWER_RE.matcher(line);
if (m1.find()) {
answers.add(m1.group(1).trim());
}
Matcher m2 = EQUAL_RE.matcher(line);
if (m2.find()) {
equals.add(m2.group(1).trim());
}
}
if (!answers.isEmpty()) {
return answers.get(answers.size() - 1);
}
if (!equals.isEmpty()) {
return equals.get(equals.size() - 1);
}
return nonEmpty.get(nonEmpty.size() - 1);
}
private static String pickCompiler() throws Exception {
for (String compiler : List.of("clang++", "g++")) {
Process probe = new ProcessBuilder("bash", "-lc", "command -v " + compiler)
.redirectErrorStream(true)
.start();
String out = new String(probe.getInputStream().readAllBytes());
int rc = probe.waitFor();
if (rc == 0 && !out.trim().isEmpty()) {
return compiler;
}
}
throw new RuntimeException("No C++ compiler found (clang++/g++).");
}
private static Path cppSource(Path root) {
return root.resolve("solutionsCpp").resolve("Euler729.cpp");
}
private static boolean shouldSkipCheckpoints(Path root) {
Path src = cppSource(root);
try {
String text = Files.readString(src);
return text.contains("--skip-checkpoints");
} catch (Exception ex) {
return false;
}
}
private static Path ensureBridgeBinary() throws Exception {
Path root = Paths.get(System.getProperty("user.dir"));
Path src = cppSource(root);
Path bin = root.resolve("solutionsCpp").resolve(".euler729_java_bridge");
boolean rebuild = Files.notExists(bin)
|| Files.getLastModifiedTime(src).compareTo(Files.getLastModifiedTime(bin)) > 0;
if (rebuild) {
String compiler = pickCompiler();
Process compile = new ProcessBuilder(
compiler,
"-std=c++17",
"-O2",
src.toString(),
"-o",
bin.toString())
.inheritIO()
.start();
if (compile.waitFor() != 0) {
throw new RuntimeException("Failed to compile Euler729 C++ bridge.");
}
}
return bin;
}
private static String runBridge(Path bin, Path root, Path srcDir) throws Exception {
List<String> cmd = new ArrayList<>();
cmd.add(bin.toString());
if (shouldSkipCheckpoints(root)) {
cmd.add("--skip-checkpoints");
}
Process first = new ProcessBuilder(cmd)
.directory(root.toFile())
.redirectErrorStream(true)
.start();
String out = new String(first.getInputStream().readAllBytes());
int rc = first.waitFor();
if (rc == 0) {
return out;
}
Process second = new ProcessBuilder(cmd)
.directory(srcDir.toFile())
.redirectErrorStream(true)
.start();
String out2 = new String(second.getInputStream().readAllBytes());
int rc2 = second.waitFor();
if (rc2 == 0) {
return out2;
}
throw new RuntimeException("Euler729 C++ bridge failed.\n" + out + "\n" + out2);
}
private static String solveViaCppBridge() throws Exception {
Path root = Paths.get(System.getProperty("user.dir"));
Path src = cppSource(root);
Path bin = ensureBridgeBinary();
String out = runBridge(bin, root, src.getParent());
String parsed = parseOutput(out);
if (parsed.isEmpty()) {
throw new RuntimeException("Euler729 C++ bridge produced empty output.");
}
return parsed;
}
public static void main(String[] args) throws Exception {
System.out.println(solveViaCppBridge());
}
}