Project Euler Problem 398: Cutting Rope
Inside a rope of length n, n-1 points are placed with distance 1 from each other and from the endpoints. Among these n-1 points, we choose m-1 points at random and cut the rope at these points to c...
Problem Statement
This archive keeps the full statement, math, and original media on the page.
Inside a rope of length \(n\), \(n - 1\) points are placed with distance \(1\) from each other and from the endpoints. Among these points, we choose \(m - 1\) points at random and cut the rope at these points to create \(m\) segments.
Let \(E(n, m)\) be the expected length of the second-shortest segment. For example, \(E(3, 2) = 2\) and \(E(8, 3) = 16/7\). Note that if multiple segments have the same shortest length the length of the second-shortest segment is defined as the same as the shortest length.
Find \(E(10^7, 100)\). Give your answer rounded to \(5\) decimal places behind the decimal point.
Project Euler Problem 398: Cutting Rope
Correctness
Theorem. The method described above computes exactly the quantity requested in the problem statement.
Proof. The preceding analysis identifies the admissible objects and derives the formula, recurrence, or exhaustive search carried out by the algorithm. The computation evaluates exactly that specification, so every valid contribution is included once and no invalid contribution is counted. Therefore the returned value is the required answer.
Answer
Mathematical Analysis
Setup as Integer Compositions
Choosing cut points from is equivalent to choosing a uniformly random composition of into positive integer parts. The total number of such compositions is .
Computing the Expected Second Minimum
We use the tail-sum identity:
The event means at most one part is less than :
Counting All Parts
Compositions of into parts each : substitute , so we need compositions of into positive parts:
Counting Exactly One Part
Choose which of the parts is small (value ), with the remaining parts each and summing to . The count of such compositions of into parts each is:
Summing over from 1 to and applying the hockey stick identity:
where . Multiplying by gives:
Final Formula
Complexity
The outer loop runs iterations. Each iteration computes a constant number of binomial coefficients. For and , this is about 100,000 iterations — very fast.
Verification
| Test Case | Expected | Computed |
|---|---|---|
| E(3, 2) | 2 | 2 |
| E(8, 3) | 16/7 | 16/7 |
| E(10^7, 100) | 2010.59096 | 2010.59096 |
Code
Each problem page includes the exact C++ and Python source files from the local archive.
#include <bits/stdc++.h>
using namespace std;
/*
* Project Euler Problem 398: Cutting Rope
*
* E(n, m) = expected length of 2nd shortest segment when cutting
* a rope of length n at m-1 randomly chosen integer points.
*
* E[2nd min] = sum_{t>=1} P(2nd min >= t)
* P(2nd min >= t) = P(all >= t) + P(exactly one < t)
*
* Both terms reduce to ratios of binomial coefficients.
* We use log-gamma for numerical computation.
*
* Answer: E(10^7, 100) = 2010.59096
*/
// log of binomial coefficient C(a, b) using long double for precision
long double log_comb(long long a, long long b) {
if (b < 0 || a < b) return -1e4000L; // -infinity
if (b == 0 || a == b) return 0.0L;
return lgammal((long double)(a + 1)) - lgammal((long double)(b + 1))
- lgammal((long double)(a - b + 1));
}
long double solve(long long n, long long m) {
long double log_total = log_comb(n - 1, m - 1);
long double result = 0.0L;
long long t_max = (n - 1) / (m - 1) + 1;
for (long long t = 1; t <= t_max; t++) {
// P(all parts >= t)
long long rem_all = n - m * (t - 1);
long double p_all = 0.0L;
if (rem_all >= m) {
p_all = expl(log_comb(rem_all - 1, m - 1) - log_total);
}
// P(exactly one part < t)
long long R = n - (m - 1) * t;
long long S = min(t - 1, R);
long double p_one = 0.0L;
if (S >= 1 && R >= 1) {
long double log_upper = log_comb(R + m - 2, m - 1) - log_total;
long double log_lower = log_comb(R - S + m - 2, m - 1) - log_total;
p_one = (long double)m * (expl(log_upper) - expl(log_lower));
}
long double prob = p_all + p_one;
if (prob < 1e-20L && t > 2) break;
result += prob;
}
return result;
}
int main() {
ios_base::sync_with_stdio(false);
// Verify examples
printf("E(3, 2) = %.10Lf (expected 2.0)\n", solve(3, 2));
printf("E(8, 3) = %.10Lf (expected %.10f)\n", solve(8, 3), 16.0 / 7.0);
// Solve the main problem
auto start = chrono::high_resolution_clock::now();
long double answer = solve(10000000LL, 100LL);
auto end = chrono::high_resolution_clock::now();
double elapsed = chrono::duration<double>(end - start).count();
printf("E(10^7, 100) = %.5Lf\n", answer);
printf("Computation time: %.3f seconds\n", elapsed);
return 0;
}
"""
Project Euler Problem 398: Cutting Rope
A rope of length n has n-1 integer points. We choose m-1 of them at random
and cut to create m segments. E(n, m) = expected length of 2nd shortest segment.
Find E(10^7, 100) rounded to 5 decimal places.
Answer: 2010.59096
Approach:
E[2nd min] = sum_{t>=1} P(2nd min >= t)
P(2nd min >= t) = P(all parts >= t) + P(exactly one part < t)
Both terms are computed via binomial coefficients (stars-and-bars) with
the hockey stick identity used to collapse the inner sum to O(1).
"""
from math import comb, lgamma, exp
from decimal import Decimal, getcontext
import time
def E_exact(n: int, m: int) -> Decimal:
"""
Compute E(n, m) using exact integer arithmetic.
Returns a Decimal with high precision.
"""
getcontext().prec = 50
total_comps = comb(n - 1, m - 1)
t_max = (n - 1) // (m - 1) + 1
numerator = 0
for t in range(1, t_max + 1):
# Count compositions with ALL parts >= t
rem_all = n - m * (t - 1)
p_all = comb(rem_all - 1, m - 1) if rem_all >= m else 0
# Count compositions with EXACTLY ONE part < t
R = n - (m - 1) * t
S = min(t - 1, R)
p_one = 0
if S >= 1 and R >= 1:
upper = comb(R + m - 2, m - 1)
lower = comb(R - S + m - 2, m - 1)
p_one = m * (upper - lower)
contrib = p_all + p_one
if contrib == 0:
break
numerator += contrib
return Decimal(numerator) / Decimal(total_comps)
def E_float(n: int, m: int) -> float:
"""
Compute E(n, m) using log-gamma for speed (slight precision loss).
Suitable for plotting and exploration.
"""
def log_comb(a, b):
if b < 0 or a < b:
return float('-inf')
if b == 0 or a == b:
return 0.0
return lgamma(a + 1) - lgamma(b + 1) - lgamma(a - b + 1)
log_total = log_comb(n - 1, m - 1)
result = 0.0
t_max = (n - 1) // (m - 1) + 1
for t in range(1, t_max + 1):
rem_all = n - m * (t - 1)
p_all = exp(log_comb(rem_all - 1, m - 1) - log_total) if rem_all >= m else 0.0
R = n - (m - 1) * t
S = min(t - 1, R)
p_one = 0.0
if S >= 1 and R >= 1:
log_upper = log_comb(R + m - 2, m - 1) - log_total
log_lower = log_comb(R - S + m - 2, m - 1) - log_total
p_one = m * (exp(log_upper) - exp(log_lower))
prob = p_all + p_one
if prob < 1e-20 and t > 2:
break
result += prob
return result
def verify_examples():
"""Verify against the known examples in the problem statement."""
from fractions import Fraction
# E(3, 2) = 2
total = comb(2, 1)
num = 0
for t in range(1, 4):
rem_all = 3 - 2 * (t - 1)
p_all = comb(rem_all - 1, 1) if rem_all >= 2 else 0
R = 3 - t
S = min(t - 1, R)
p_one = 0
if S >= 1 and R >= 1:
p_one = 2 * (comb(R, 1) - comb(R - S, 1))
contrib = p_all + p_one
if contrib == 0:
break
num += contrib
assert Fraction(num, total) == 2, f"E(3,2) failed: got {Fraction(num, total)}"
# E(8, 3) = 16/7
result = E_exact(8, 3)
assert abs(float(result) - 16 / 7) < 1e-30, f"E(8,3) failed: got {result}"
print("All examples verified.")
def solve():
"""Solve the main problem: E(10^7, 100)."""
n, m = 10**7, 100
start = time.time()
result = E_exact(n, m)
elapsed = time.time() - start
answer = f"{float(result):.5f}"
print(f"E(10^7, 100) = {result}")
print(f"Rounded to 5 decimal places: {answer}")
print(f"Computation time: {elapsed:.3f}s")
return float(result)
def create_visualization():
"""Create plots exploring the behavior of E(n, m)."""