The Master Theorem — Practice Tasks¶
All tasks must be solved in Go, Java, and Python. Each task ships with a precise spec and starter code in all three languages. Implement the Master-Theorem (or Akra–Bazzi) logic and validate against the evaluation criteria. Always cross-check the closed-form answer against a recursion-tree summation on small
nbefore trusting it.
Beginner Tasks (5)¶
Task 1 — Compute the critical exponent¶
Problem. Given integers a ≥ 1 and b > 1, compute the critical exponent log_b a and print it to 4 decimal places.
Input / Output spec. - Read a, b. - Print log_b a rounded to 4 decimals.
Constraints. - 1 ≤ a ≤ 10^6, 2 ≤ b ≤ 10^6. - Use ln(a)/ln(b), not a custom log base.
Hint. Change-of-base: log_b a = math.Log(a)/math.Log(b). Verify b^(log_b a) ≈ a.
Starter — Go.
package main
import (
"fmt"
"math"
)
func critExp(a, b float64) float64 {
// TODO: return log_b a
_ = math.Log
return 0
}
func main() {
var a, b float64
fmt.Scan(&a, &b)
fmt.Printf("%.4f\n", critExp(a, b))
}
Starter — Java.
import java.util.Scanner;
public class Task1 {
static double critExp(double a, double b) {
// TODO: return log_b a
return 0;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
double a = sc.nextDouble(), b = sc.nextDouble();
System.out.printf("%.4f%n", critExp(a, b));
}
}
Starter — Python.
import math
def crit_exp(a, b):
# TODO: return log_b a
return 0.0
if __name__ == "__main__":
a, b = map(float, input().split())
print(f"{crit_exp(a, b):.4f}")
Task 2 — Classify into one of the three cases¶
Problem. Given a, b, and the exponent c of f(n) = n^c, print 1, 2, or 3 for the Master-Theorem case.
Input / Output spec. - Read a, b, c. - Print the case number.
Constraints. - Compare c to log_b a with an epsilon of 1e-9.
Hint. c < p → 1, c ≈ p → 2, c > p → 3.
Starter — Go.
package main
import (
"fmt"
"math"
)
func caseOf(a, b, c float64) int {
p := math.Log(a) / math.Log(b)
// TODO: return 1, 2, or 3
_ = p
return 0
}
func main() {
var a, b, c float64
fmt.Scan(&a, &b, &c)
fmt.Println(caseOf(a, b, c))
}
Starter — Java.
import java.util.Scanner;
public class Task2 {
static int caseOf(double a, double b, double c) {
double p = Math.log(a) / Math.log(b);
// TODO: return 1, 2, or 3
return 0;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println(caseOf(sc.nextDouble(), sc.nextDouble(), sc.nextDouble()));
}
}
Starter — Python.
import math
def case_of(a, b, c):
p = math.log(a) / math.log(b)
# TODO: return 1, 2, or 3
return 0
if __name__ == "__main__":
a, b, c = map(float, input().split())
print(case_of(a, b, c))
Task 3 — Print the closed-form T(n)¶
Problem. Given a, b, c for T(n) = a T(n/b) + n^c, print the closed-form T(n) string (e.g. Theta(n^1.0000 log n)).
I/O spec. Read a, b, c; print the Theta(...) string.
Constraints. Handle all three cases; Case 2 must include the log n factor.
Hint. Reuse Task 2's classifier, then format per case.
Starter — Go.
package main
import (
"fmt"
"math"
)
func closedForm(a, b, c float64) string {
p := math.Log(a) / math.Log(b)
_ = p
// TODO: return the Theta(...) string for the right case
return ""
}
func main() {
var a, b, c float64
fmt.Scan(&a, &b, &c)
fmt.Println(closedForm(a, b, c))
}
Starter — Java.
import java.util.Scanner;
public class Task3 {
static String closedForm(double a, double b, double c) {
double p = Math.log(a) / Math.log(b);
// TODO
return "";
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println(closedForm(sc.nextDouble(), sc.nextDouble(), sc.nextDouble()));
}
}
Starter — Python.
import math
def closed_form(a, b, c):
p = math.log(a) / math.log(b)
# TODO
return ""
if __name__ == "__main__":
a, b, c = map(float, input().split())
print(closed_form(a, b, c))
Task 4 — Recursion-tree leaf count¶
Problem. Given a, b, n (with n a power of b), compute the number of leaves a^(log_b n) and confirm it equals n^(log_b a). Print both.
I/O spec. Read a, b, n; print leaf count via both formulas (should match).
Constraints. n is a power of b; use floating point and round.
Hint. levels = log_b n; leaves = a^levels. Separately n^(log_b a).
Starter — Go.
package main
import (
"fmt"
"math"
)
func main() {
var a, b, n float64
fmt.Scan(&a, &b, &n)
// TODO: levels = log_b n; print a^levels and n^(log_b a)
_ = math.Log
}
Starter — Java.
import java.util.Scanner;
public class Task4 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
double a = sc.nextDouble(), b = sc.nextDouble(), n = sc.nextDouble();
// TODO
}
}
Starter — Python.
import math
if __name__ == "__main__":
a, b, n = map(float, input().split())
# TODO: print a^(log_b n) and n^(log_b a)
Task 5 — Sum one recursion-tree level¶
Problem. Given a, b, level index i, and n, compute the total work at level i: a^i · f(n/b^i) for f(n) = n.
I/O spec. Read a, b, i, n; print a^i * (n / b^i).
Constraints. 0 ≤ i ≤ 60.
Hint. This single term is the geometric-series term; for merge sort (a=b=2, f=n) every level equals n.
Starter — Go.
package main
import (
"fmt"
"math"
)
func levelWork(a, b, i, n float64) float64 {
// TODO: a^i * (n / b^i)
return math.Pow(a, i) * 0
}
func main() {
var a, b, i, n float64
fmt.Scan(&a, &b, &i, &n)
fmt.Printf("%.4f\n", levelWork(a, b, i, n))
}
Starter — Java.
import java.util.Scanner;
public class Task5 {
static double levelWork(double a, double b, double i, double n) {
// TODO
return 0;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.printf("%.4f%n", levelWork(sc.nextDouble(), sc.nextDouble(), sc.nextDouble(), sc.nextDouble()));
}
}
Starter — Python.
def level_work(a, b, i, n):
# TODO: a**i * (n / b**i)
return 0.0
if __name__ == "__main__":
a, b, i, n = map(float, input().split())
print(f"{level_work(a, b, i, n):.4f}")
Intermediate Tasks (4)¶
Task 6 — Extended Master-Theorem classifier (with k)¶
Problem. Given a, b, c, k for f(n) = n^c log^k n, print the closed-form T(n) including extended Case 2 (log^(k+1) n).
I/O spec. Read a, b, c, k; print the Theta(...) string.
Constraints. Support k ≥ 0 for the extended Case 2; for Case 2 with k=0 the result is n^p log n.
Hint. When c == log_b a, the answer is n^p log^(k+1) n.
Starter — Go.
package main
import (
"fmt"
"math"
)
func extended(a, b, c, k float64) string {
p := math.Log(a) / math.Log(b)
_ = p
// TODO: Case 1 -> n^p ; Case 2 -> n^p log^(k+1) n ; Case 3 -> n^c log^k n
return ""
}
func main() {
var a, b, c, k float64
fmt.Scan(&a, &b, &c, &k)
fmt.Println(extended(a, b, c, k))
}
Starter — Java.
import java.util.Scanner;
public class Task6 {
static String extended(double a, double b, double c, double k) {
double p = Math.log(a) / Math.log(b);
// TODO
return "";
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println(extended(sc.nextDouble(), sc.nextDouble(), sc.nextDouble(), sc.nextDouble()));
}
}
Starter — Python.
import math
def extended(a, b, c, k):
p = math.log(a) / math.log(b)
# TODO
return ""
if __name__ == "__main__":
a, b, c, k = map(float, input().split())
print(extended(a, b, c, k))
Task 7 — Recursion-tree summation vs closed form¶
Problem. Sum the full recursion tree Σ a^i f(n/b^i) plus leaves for f(n) = n^c, then divide by the predicted closed form and verify the ratio is within [0.1, 10] (constant factor).
I/O spec. Read a, b, c, n; print the tree sum, the closed form, and the ratio.
Constraints. n up to 2^20.
Hint. Loop while size >= 1, accumulating nodes * size^c; nodes *= a, size /= b.
Starter — Go.
package main
import (
"fmt"
"math"
)
func treeSum(a, b, c, n float64) float64 {
total, size, nodes := 0.0, n, 1.0
for size >= 1 {
// TODO: total += nodes * size^c ; update size, nodes
_ = math.Pow
break
}
return total
}
func main() {
var a, b, c, n float64
fmt.Scan(&a, &b, &c, &n)
fmt.Printf("%.3e\n", treeSum(a, b, c, n))
}
Starter — Java.
import java.util.Scanner;
public class Task7 {
static double treeSum(double a, double b, double c, double n) {
double total = 0, size = n, nodes = 1;
while (size >= 1) {
// TODO
break;
}
return total;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.printf("%.3e%n", treeSum(sc.nextDouble(), sc.nextDouble(), sc.nextDouble(), sc.nextDouble()));
}
}
Starter — Python.
def tree_sum(a, b, c, n):
total, size, nodes = 0.0, float(n), 1.0
while size >= 1:
# TODO: total += nodes * size**c ; size /= b ; nodes *= a
break
return total
if __name__ == "__main__":
a, b, c, n = map(float, input().split())
print(f"{tree_sum(a, b, c, n):.3e}")
Task 8 — Regularity-condition checker¶
Problem. For Case-3 candidates (c > log_b a), verify the regularity condition by computing the ratio a·f(n/b)/f(n) = a/b^c and confirming it is < 1. Print OK or FAIL.
I/O spec. Read a, b, c; print OK if a/b^c < 1, else FAIL.
Constraints. Only meaningful when c > log_b a; print N/A otherwise.
Hint. For f(n) = n^c, regularity reduces to a/b^c < 1, equivalent to c > log_b a.
Starter — Go.
package main
import (
"fmt"
"math"
)
func regularity(a, b, c float64) string {
p := math.Log(a) / math.Log(b)
if c <= p {
return "N/A"
}
// TODO: ratio = a/b^c ; return OK if < 1 else FAIL
return ""
}
func main() {
var a, b, c float64
fmt.Scan(&a, &b, &c)
fmt.Println(regularity(a, b, c))
}
Starter — Java.
import java.util.Scanner;
public class Task8 {
static String regularity(double a, double b, double c) {
double p = Math.log(a) / Math.log(b);
if (c <= p) return "N/A";
// TODO
return "";
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println(regularity(sc.nextDouble(), sc.nextDouble(), sc.nextDouble()));
}
}
Starter — Python.
import math
def regularity(a, b, c):
p = math.log(a) / math.log(b)
if c <= p:
return "N/A"
# TODO: ratio = a / b**c ; "OK" if < 1 else "FAIL"
return ""
if __name__ == "__main__":
a, b, c = map(float, input().split())
print(regularity(a, b, c))
Task 9 — Empirical asymptotic verifier¶
Problem. Given a recursive op-count function T(n) = a·T(n/b) + n^c (with T(1)=0), evaluate it at doubling sizes and divide by the predicted closed form; print the ratios (they should converge to a constant).
I/O spec. Read a, b, c; for n = 2^4 … 2^14, print n and T(n)/closed(n).
Constraints. Use integer n//b in the recursion.
Hint. Memoize or just recompute; sizes are small.
Starter — Go.
package main
import (
"fmt"
"math"
)
func T(a, b, c, n float64) float64 {
if n <= 1 {
return 0
}
// TODO: a*T(a,b,c, floor(n/b)) + n^c
_ = math.Pow
return 0
}
func main() {
var a, b, c float64
fmt.Scan(&a, &b, &c)
for k := 4; k <= 14; k++ {
n := math.Pow(2, float64(k))
fmt.Printf("n=%6.0f T=%g\n", n, T(a, b, c, n))
}
}
Starter — Java.
import java.util.Scanner;
public class Task9 {
static double T(double a, double b, double c, double n) {
if (n <= 1) return 0;
// TODO
return 0;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
double a = sc.nextDouble(), b = sc.nextDouble(), c = sc.nextDouble();
for (int k = 4; k <= 14; k++) {
double n = Math.pow(2, k);
System.out.printf("n=%6.0f T=%g%n", n, T(a, b, c, n));
}
}
}
Starter — Python.
import math
def T(a, b, c, n):
if n <= 1:
return 0
# TODO: a * T(a, b, c, n // b) + n**c
return 0
if __name__ == "__main__":
a, b, c = map(float, input().split())
for k in range(4, 15):
n = 2 ** k
print(f"n={n:6d} T={T(a, b, c, n)}")
Advanced Tasks (3)¶
Task 10 — Akra–Bazzi exponent by bisection¶
Problem. Given lists a[] and subproblem fractions frac[] (each < 1), solve Σ a_i · frac_i^p = 1 for p via bisection to 6-decimal precision.
I/O spec. Read m, then m pairs a_i frac_i; print p.
Constraints. phi(p) = Σ a_i frac_i^p is strictly decreasing; bracket p ∈ [-10, 50].
Hint. Standard bisection: if phi(mid) > 1 raise the lower bound, else lower the upper.
Starter — Go.
package main
import (
"fmt"
"math"
)
func akraBazzi(a, frac []float64) float64 {
phi := func(p float64) float64 {
s := 0.0
for i := range a {
s += a[i] * math.Pow(frac[i], p)
}
return s
}
lo, hi := -10.0, 50.0
// TODO: 200 bisection iterations on phi(p) == 1
_ = phi
return (lo + hi) / 2
}
func main() {
var m int
fmt.Scan(&m)
a := make([]float64, m)
frac := make([]float64, m)
for i := 0; i < m; i++ {
fmt.Scan(&a[i], &frac[i])
}
fmt.Printf("%.6f\n", akraBazzi(a, frac))
}
Starter — Java.
import java.util.Scanner;
public class Task10 {
static double akraBazzi(double[] a, double[] frac) {
double lo = -10, hi = 50;
// TODO: bisection on phi(p)=sum a_i frac_i^p == 1
return (lo + hi) / 2;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int m = sc.nextInt();
double[] a = new double[m], frac = new double[m];
for (int i = 0; i < m; i++) { a[i] = sc.nextDouble(); frac[i] = sc.nextDouble(); }
System.out.printf("%.6f%n", akraBazzi(a, frac));
}
}
Starter — Python.
def akra_bazzi(a, frac):
def phi(p):
return sum(ai * (fi ** p) for ai, fi in zip(a, frac))
lo, hi = -10.0, 50.0
# TODO: 200 bisection iterations on phi(p) == 1
return (lo + hi) / 2
if __name__ == "__main__":
m = int(input())
a, frac = [], []
for _ in range(m):
ai, fi = map(float, input().split())
a.append(ai)
frac.append(fi)
print(f"{akra_bazzi(a, frac):.6f}")
Task 11 — Full Akra–Bazzi solver with the integral¶
Problem. After finding p, evaluate ∫_1^n g(u)/u^(p+1) du numerically for g(u) = u and classify T(n) as Θ(n^p) (integral converges), Θ(n^p log n) (integral ~ log), or Θ(n^(integral exponent)).
I/O spec. Read m, the pairs, and n; print the closed-form class.
Constraints. Use the analytic integral for g(u)=u: ∫ u^(−p) du.
Hint. If p > 1: integral converges → Θ(n^p). If p = 1: → Θ(n log n). If p < 1: → Θ(n).
Starter — Go.
package main
import (
"fmt"
"math"
)
func classifyAB(p float64) string {
const eps = 1e-6
// TODO: g(u)=u case: p>1 -> n^p ; p==1 -> n log n ; p<1 -> n
_ = math.Abs
_ = eps
return ""
}
func main() {
var p float64
fmt.Scan(&p)
fmt.Println(classifyAB(p))
}
Starter — Java.
import java.util.Scanner;
public class Task11 {
static String classifyAB(double p) {
final double eps = 1e-6;
// TODO
return "";
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println(classifyAB(sc.nextDouble()));
}
}
Starter — Python.
def classify_ab(p):
eps = 1e-6
# TODO: g(u)=u: p>1 -> n^p ; p==1 -> n log n ; p<1 -> n
return ""
if __name__ == "__main__":
p = float(input())
print(classify_ab(p))
Task 12 — Recurrence parser and dispatcher¶
Problem. Parse a recurrence string like 2T(n/2)+n or T(n/3)+T(2n/3)+n and dispatch: single-term → Master Theorem; multi-term → Akra–Bazzi. Print the Θ bound.
I/O spec. Read a recurrence string; print the closed form.
Constraints. Support the f(n) forms 1, n, n^2, n*log(n). Single and double subproblem terms.
Hint. Tokenize the aT(n/b) terms with a regex; collect (a_i, b_i); if exactly one term use the Master Theorem, else Akra–Bazzi.
Starter — Go.
package main
import (
"fmt"
"regexp"
)
func solve(rec string) string {
re := regexp.MustCompile(`(\d*)T\(n/(\d+(?:\.\d+)?)\)`)
matches := re.FindAllStringSubmatch(rec, -1)
_ = matches
// TODO: collect (a_i, b_i); single -> Master Theorem; multi -> Akra-Bazzi
return ""
}
func main() {
var rec string
fmt.Scan(&rec)
fmt.Println(solve(rec))
}
Starter — Java.
import java.util.Scanner;
import java.util.regex.*;
public class Task12 {
static String solve(String rec) {
Matcher m = Pattern.compile("(\\d*)T\\(n/(\\d+(?:\\.\\d+)?)\\)").matcher(rec);
// TODO: collect terms; dispatch Master Theorem vs Akra-Bazzi
return "";
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println(solve(sc.next()));
}
}
Starter — Python.
import re
def solve(rec):
terms = re.findall(r"(\d*)T\(n/(\d+(?:\.\d+)?)\)", rec)
# TODO: collect (a_i, b_i); single -> Master Theorem; multi -> Akra-Bazzi
return ""
if __name__ == "__main__":
print(solve(input().strip()))
Evaluation Criteria¶
| Task group | What graders check |
|---|---|
| Beginner | Correct log_b a, correct case selection, correct closed-form formatting. |
| Intermediate | Extended Case 2 adds exactly one log; tree sum matches closed form within a constant; regularity ratio computed as a/b^c. |
| Advanced | Akra–Bazzi p correct to 6 decimals; integral classification matches the convergent/borderline/divergent rule; parser dispatches single vs multi-term correctly. |
Test discipline. - For every solver, hand-verify against the famous results: merge sort → Θ(n log n), Karatsuba → Θ(n^1.585), Strassen → Θ(n^2.807). - For Akra–Bazzi, confirm T(n/3)+T(2n/3)+n gives p=1 and T(n/5)+T(7n/10)+n gives p<1 (linear). - Always sum a small recursion tree as an independent oracle before trusting a closed form.