This Python encapsulation of the MiniZinc model (using the **minizinc.py** library) executes in 162ms.

# Team B has 2 points. They cannot have won or drawn any of their # matches, so their points come from the goals scored, and they must have # scored at least one goal in each match. Only one match is unplayed, # so B must have played 2 of their matches, lost them both and scored # a single goal in each match. # # But B cannot have lost to A, as A only has 9 points. So the A vs B # match must be the unplayed match. # # So suppose the scores in the matches are: # # A v B = not played # A v C = a - b # A v D = c - d # B v C = e - f # B v D = g - h # C v D = i - j # # where a, b, c, d, e, f, g, h, i, j have values between 1 and 6. from enigma import printf from minizinc import MiniZinc, var # create the model m = MiniZinc(f""" % each side scores at least one goal in each match % but no more than 7 goals are scored in any match {var("1..6", "abcdefghij")}; constraint a + b < 8 /\ c + d < 8 /\ e + f < 8 /\ g + h < 8 /\ i + j < 8; % points for a match (X vs Y = x - y) function var int: points(var int: x, var int: y) = x + 10 * (x > y) + 5 * (x = y); % total points for A constraint points(a, b) + points(c, d) = 9; % total points for B constraint points(e, f) + points(g, h) = 2; % total points for C constraint points(b, a) + points(f, e) + points(i, j) = 24; % total points for D constraint points(d, c) + points(h, g) + points(j, i) = 34; solve satisfy; """) # solve the model for (a, b, c, d, e, f, g, h, i, j) in m.solve(result="a b c d e f g h i j"): printf("A v B = not played") printf("A v C = {a} - {b}") printf("A v D = {c} - {d}") printf("B v C = {e} - {f}") printf("B v D = {g} - {h}") printf("C v D = {i} - {j}") printf()]]>

This Python program runs in 665ms.

**Run:** [ @repl.it ]

from itertools import product from enigma import Football, printf # the scoring system (points for goals are dealt with separately) football = Football(points={ 'w': 10, 'd': 5 }) # consider 4 teams: W, X, Y, Z # the matches are: WX, WY, WZ, XY, XZ, YZ # but one of the matches isn't played, say WX. WX = None # the remaining matches are played, each team scores at least # one goal in each match, but no more than 7 goals are scored in # any match, so possible scores are: scores = ( (1, 1), (1, 2), (2, 1), (1, 3), (2, 2), (3, 1), (1, 4), (2, 3), (3, 2), (4, 1), (1, 5), (2, 4), (3, 3), (4, 2), (5, 1), (1, 6), (2, 5), (3, 4), (4, 3), (5, 2), (6, 1), ) # the points we are looking for # (which are all different, so can be used as keys) pts = { 9: 'A' , 2: 'B', 24: 'C', 34: 'D' } # find the points from the scores def points(ss, ts): t = football.table(football.outcomes(ss), ts) return t.points + sum(s[x] for (s, x) in zip(ss, ts) if s is not None) # choose the scores in W's played matches for (WY, WZ) in product(scores, repeat=2): pW = points([WX, WY, WZ], [0, 0, 0]) # which team is W? W = pts.get(pW) if W is None: continue # choose scores for for X's remaining matches for (XY, XZ) in product(scores, repeat=2): pX = points([WX, XY, XZ], [1, 0, 0]) if not(pX < pW): continue X = pts.get(pX) if X is None: continue # choose the score in the remaining match for YZ in scores: pY = points([WY, XY, YZ], [1, 1, 0]) pZ = points([WZ, XZ, YZ], [1, 1, 1]) if not(pY < pZ): continue Y = pts.get(pY) Z = pts.get(pZ) if Y is None or Z is None: continue if len(set([W, X, Y, Z])) != 4: continue # output the scores printf("{W} v {X} = not played") printf("{W} v {Y} = {WY}") printf("{W} v {Z} = {WZ}") printf("{X} v {Y} = {XY}") printf("{X} v {Z} = {XZ}") printf("{Y} v {Z} = {YZ}") printf("points: {W} = {pW}, {X} = {pX}, {Y} = {pY}, {Z} = {pZ}") printf()

**Solution:** The scores in the played matches are: A vs C = 1-1; A vs D = 3-4; B vs C = 1-2; B vs D = 1-4; C vs D = 1-1.

The A vs B match has not yet been played.

]]>There aren’t many examples of multiplicative partition programs on the web, as it hasn’t had the same amount of attention as additive partitions. Early studies of multiplicative partitions went under the name “factorisatio numerorum”, which sounds like a Harry Potter spell.

Disappontingly, as Jim has noted, a multiplicative partition function isn’t needed, as all the ages of Mrs Hill and Hillocks are prime or one.

from factors import multipart, factorise # There are an even number of Hills, excepting Mr Hill, as the sum of their ages # is even # and at least 3 Hills other than Mr Hill (Mrs Hill and at least 2 Hillocks) for n in xrange(4,17,2): ageprod = (10**n-1)/9 f = factorise(ageprod) if max(f) <100: for mp in multipart(ageprod): if max(mp)<100: if len(mp)%2: mp.append(1) if len(mp)==n: mrhill = sum(mp) print "Mr Hill is",mrhill print "Mrs Hill is", max(mp) mp.remove(max(mp)) print "The Hillocks are", sorted(mp)]]>

from itertools import product from enigma import printf # possible scores scores = ( (10, 0), (6, 2), (5, 5), (2, 6), (0, 10), (0, 0) ) # possible the matches for (AB, AC, AD, BC, BD, CD) in product(scores, repeat=6): A = AB[0] + AC[0] + AD[0] B = AB[1] + BC[0] + BD[0] C = AC[1] + BC[1] + CD[0] D = AD[1] + BD[1] + CD[1] if A != 21 or B != 10 or C != 9 or D != 6: continue printf("AB={AB} AC={AC} AD={AD} BC={BC} BD={BD} CD={CD}")]]>

1. One team wins (10 points), the other team loses (0 points).

2. The match is drawn, but one team wins on the first innings (6 points), the other team loses on the first innings (2 points).

3. The match is tied (5 points to each side).

We can use the `Football()` helper class from the **enigma.py** library to construct an appropriate scoring system.

We could consider all possibilities for all 6 matches in one go, but the following code starts with the matches for lowest scoring team and works upwards.

This Python program runs in 117ms.

**Run**: [ @repl.it ]

from enigma import Football, printf # possible match outcomes # W = win (10 points) # L = lose (0 points) # w = win on 1st innings (6 points) # l = lose on 1st innings (2 points) # t = tie (5 points) # x = not played cricket = Football( games="WLwltx", points={ 'W': 10, 'w': 6, 'l': 2, 't': 5 }, swap={ 'W': 'L', 'L': 'W', 'w': 'l', 'l': 'w' } ) # choose an outcome for each of D's matches for (AD, BD, CD) in cricket.games(repeat=3): D = cricket.table([AD, BD, CD], [1, 1, 1]) if D.points != 6: continue # remaining matches for C for (AC, BC) in cricket.games(repeat=2): C = cricket.table([AC, BC, CD], [1, 1, 0]) if C.points != 9: continue # the remaining match for AB in cricket.games(): B = cricket.table([AB, BC, BD], [1, 0, 0]) A = cricket.table([AB, AC, AD], [0, 0, 0]) if B.points != 10 or A.points != 21: continue printf("AB={AB} AC={AC} AD={AD} BC={BC} BD={BD} CD={CD}")

**Solution:** The A vs B match was tied. In the A vs C match A won on the first innings. In the A vs D match A won. The B vs C match was tied. In the C vs D match D won on the first innings.

The B vs D match is not yet played.

]]>If the numerator of the fraction is *x*, we can derive the integer equation:

(10^8 – 1)x = TIME × ANDAGAIN

Using the extra optional clue, we can consider numerators of the form *abb*, which means we only have to consider two digit numbers of the form *ab* and then we can just add an extra *b* digit to the end. So we only have to check 90 values for *x*.

This Python program runs in 97ms.

**Run:** [ @repl.it ]

from enigma import irange, divisors_pairs, gcd, nsplit, printf # numerator x in the form abb (it is possible that a = b) for ab in irange(10, 99): x = (ab * 10) + (ab % 10) for (TIME, ANDAGAIN) in divisors_pairs(99999999 * x): # TIME has 4 digits if TIME < 1000: continue if TIME > 9999: break # and x / TIME is a fraction in its simplest form if gcd(x, TIME) != 1: continue # TIME consists of 4 distinct digits (T, I, M, E) = nsplit(TIME) if not(len(set((T, I, M, E))) == 4): continue # check the digits in ANDAGAIN ds = nsplit(ANDAGAIN) if len(ds) != 8: continue (A, N, D, A1, G, A2, I1, N1) = ds if not(A == A1 == A2 and N == N1 and I == I1 and len(set((T, I, M, E, A, N, D, G))) == 8): continue printf("AGAIN = {AGAIN} [{x} / {TIME} = 0.({ANDAGAIN})...]", AGAIN=ANDAGAIN % 100000)

**Solution:** AGAIN = 38354.

The actual expression is:

522 / 1507 = 0.(34638354)…

where the digits in brackets repeat indefinitely.

Without the extra clue we can consider all three digit values of *x* from 100 to 999 (900 values), which takes the above program 289ms to consider.

But without being told anything about the “shape” of *x* we can solve the puzzle as an alphametic, and find that there is only one solution. (Even when the fraction is allowed to be not in lowest terms).

This Python program uses the `SubstitutedExpression()` solver from the **enigma.py** library and runs in 557ms.

from enigma import SubstitutedExpression, gcd, printf # express the puzzle as an alphametic expression p = SubstitutedExpression( [ "(TIME * ANDAGAIN) % 99999999 = 0" ], d2i={ 0: 'T' }, answer="(TIME, ANDAGAIN)" ) # solve the alphametic for (s, (TIME, ANDAGAIN)) in p.solve(): # extract the numerator of the fraction x = (TIME * ANDAGAIN) // 99999999 # check the fraction is in lowest terms if gcd(x, TIME) != 1: continue # output the solution printf("{x} / {TIME} = 0.({ANDAGAIN})...")]]>

I ran the program with the various target numbers (*T*) of (1, 2, 3)-triangles required by the problem:

For *T=61* it finds a solution in 277ms (but there are many other solutions).

For *T=89* it executes in 2.1s, and finds no solutions.

For *T=32* it executes in 30m, and finds no solutions.

For *T=10* it executes in 30m, and finds no solutions.

MiniZinc wouldn’t let me use the `all_different()` or `nvalue()` constraints when I was testing the vertices of the small triangles to find ones that have three different values, so I just multiply the values on the vertices together and see if we get 6, as 1×2×3 (in some order) is the only way to achieve this, it works OK.

from minizinc import MiniZinc from enigma import arg, irange, printf N = arg(10, 0, int) T = arg(61, 1, int) printf("[N={N} T={T}]\n") # create the MiniZinc model m = MiniZinc(f""" % each point in the triangle is labelled 1, 2, 3, points lying outside the triangle are labelled 0 array[0..{N}, 0..{N}] of var {{0, 1, 2, 3}}: p; constraint forall (x, y in 0..{N}) (if x + y > {N} then p[x, y] = 0 else p[x, y] != 0 endif); % the corners have specified values constraint p[0, 0] = 1; constraint p[0, {N}] = 3; constraint p[{N}, 0] = 2; % the edges values are constrained constraint forall (x in 0..{N}) (p[x, 0] = 1 \/ p[x, 0] = 2); constraint forall (y in 0..{N}) (p[0, y] = 1 \/ p[0, y] = 3); constraint forall (z in 0..{N}) (p[z, {N} - z] = 2 \/ p[z, {N} - z] = 3); % look for (1,2,3)-triangles function var {{0, 1}}: tri(var int: v1, var int: v2, var int: v3) = if v1 * v2 * v3 = 6 then 1 else 0 endif; function var {{0, 1}}: tri_up(var int: x, var int: y) = tri(p[x, y], p[x + 1, y], p[x, y + 1]); function var {{0, 1}}: tri_dn(var int: x, var int: y) = tri(p[x, y], p[x - 1, y], p[x, y - 1]); % count the total number of (1,2,3)-triangles constraint (sum (x, y in 0..{N - 1} where x + y < {N}) (tri_up(x, y))) + (sum (x, y in 1..{N} where x + y <= {N}) (tri_dn(x, y))) = {T}; solve satisfy; """) # execute the model for (p,) in m.solve(solver="mzn-chuffed", result='p'): # output solutions for y in irange(N, 0, step=-1): printf("{s}\\", s=''.ljust(y)) for x in irange(0, N - y): printf("{z} \\", z=p[x][y]) printf() printf() # stop after the first solution break else: printf("[no solutions found]")

**Solution:** **Q1:** No. **Q2:** No. **Q3:** Yes. **Q4:** No.

Here is a diagram of a solution for the *T=61* case, with the (1, 2, 3)-triangles coloured in:

Using this program for *N=10* I was able to quite quickly find that there are solutions for all odd values of *T* from 1 to 67, and there are no solutions for *T* from 87 to 100.

The only values for *T* that the program finds are the odd numbers from 1 to 73, but using the program to find the values with no solutions can take 30 minutes for some of the values. (I found that the MiniZinc model failed to find solutions for *T=69* and *T=71*, but the solution it found for *T=73* can easily be modified to provide viable solutions for these values. So it seems the MiniZinc solver may fail to find a valid solution under some circumstances).

In fact, there is a result that says for any sized triangular grid coloured in this fashion there will always be an odd-number of (1,2,3)-triangles (which means there is always at least one). The generalised result is known as Sperner’s Lemma [ https://en.wikipedia.org/wiki/Sperner%27s_lemma ].

Using this result we can immediately eliminate the cases *T=10* and *T=32*.

I’ve used the program to find the following results for various sized triangular grids:

N=1 (1 triangle, 3 points), T=[1]

N=2 (4 triangles, 6 points), T=[1]

N=3 (9 triangles, 10 points), T=[1 – 5]

N=4 (16 triangles, 15 points), T=[1 – 9]

N=5 (25 triangles, 21 points), T=[1 – 19]

N=6 (36 triangles, 28 points), T=[1 – 23]

N=7 (49 triangles, 36 points), T=[1 – 33]

N=8 (64 triangles, 45 points), T=[1 – 49]

N=9 (81 triangles, 55 points), T=[1 – 59]

N=10 (100 triangles, 66 points), T=[1 – 73]

Solutions are found for all **odd** *T* values in the indicated range.

% A Solution in MiniZinc include "globals.mzn"; % Joe's Pyramid Solution No 1 (X = 98) Solution No 2 (X = 98) % X 98 98 % A B 52 46 46 52 % C D E 32 20 26 26 20 32 % F G H I 21 11 9 17 17 9 11 21 % J K L M N 14 7 4 5 12 12 5 4 7 14 % P Q R S T U 8 6 1 3 2 10 10 2 3 1 6 8 % var 1..99:X; var 1..99:A; var 1..99:B; var 1..99:C; var 1..99:D; var 1..99:E; var 1..99:F; var 1..99:G; var 1..99:H; var 1..99:I; var 1..99:J; var 1..99:K; var 1..99:L; var 1..99:M; var 1..99:N; var 1..99:P; var 1..99:Q; var 1..99:R; var 1..99:S; var 1..99:T; var 1..99:U; constraint all_different ( [X,A,B,C,D,E,F,G,H,I,J,K,L,M,N,P,Q,R,S,T,U] ); constraint X == A + B; constraint C + D == A /\ D + E == B; constraint F + G == C /\ G + H == D /\ H + I == E; constraint J + K == F /\ K + L == G /\ L + M == H /\ M + N == I; constraint P + Q == J /\ Q + R == K /\ R + S == L /\ S + T == M /\ T + U == N; solve satisfy; output [ "[X,A,B,C,D,E,F,G,H,I,J,K,L,M,N,P,Q,R,S,T,U] = " ++ show ( [X,A,B,C,D,E,F,G,H,I,J,K,L,M,N,P,Q,R,S,T,U] ) ]; % [X, A, B, C, D, E, F, G, H, I, J, K, L, M, N, P, Q, R, S, T, U] = % [98, 52, 46, 32, 20, 26, 21, 11, 9, 17, 14, 7, 4, 5, 12, 8, 6, 1, 3, 2, 10] % 2nd solution % [X, A, B, C, D, E, F, G, H, I, J, K, L, M, N, P, Q, R, S, T, U] = % [98, 46, 52, 26, 20, 32, 17, 9, 11, 21, 12, 5, 4, 7, 14, 10, 2, 3, 1, 6, 8] % ---------- % Finished in 61msec]]>

**Run:** [ @repl.it ]

from itertools import product from enigma import printf # logical implication implies = lambda p, q: not(p) or q # do the 8 alliances satisfy the hypothetical statements def satisfy(BA, MH, SD, FS, AS, PT, LP, RA): return all([ # BA -> MH implies(BA, MH), # SD -> FS implies(SD, FS), # AS -> PT implies(AS, PT), # MH -> LP implies(MH, LP), # BA -> (not(SD) -> FS) implies(BA, implies(not(SD), FS)), # PT -> not(FS) implies(PT, not(FS)), # LP -> (RA -> PT) implies(LP, implies(RA, PT)), # MH and not(AS) -> RA implies(MH and not(AS), RA), ]) # choose a value for BA for BA in (True, False): # can we satisfy the remaining alliances for (MH, SD, FS, AS, PT, LP, RA) in product((True, False), repeat=7): if satisfy(BA, MH, SD, FS, AS, PT, LP, RA): printf("BA={BA} [MH={MH} SD={SD} FS={FS} AS={AS} PT={PT} LP={LP} RA={RA}]") break

**Solution:** It is not possible for Bosnia to ally with Austria.

But it is possible for Bosnia **not** to ally with Austria. The easiest scenario for this is the one where no-one allies with anyone else, but there are 20 scenarios for the remaining 7 alliances where Bosnia is not allied with Austria.

But here’s an alternative solution that uses the `SubstitutedExpression()` solver from the **enigma.py** library.

This run-file executes in 155ms.

**Run:** [ @repl.it ]

#!/usr/bin/env python -m enigma -r SubstitutedExpression --digits="1-9" --template="(ABC + DEF = GHI)" # the alphametic sum "ABC + DEF = GHI" # check adjacency: corners "A == 9 or A + 1 in (B, D)" "C == 9 or C + 1 in (B, F)" "G == 9 or G + 1 in (D, H)" "I == 9 or I + 1 in (F, H)" # check adjacency: edges "B == 9 or B + 1 in (A, C, E)" "D == 9 or D + 1 in (A, E, G)" "F == 9 or F + 1 in (C, E, I)" "H == 9 or H + 1 in (E, G, I)" # check adjacency: middle "E == 9 or E + 1 in (B, D, F, H)"

**Solution:** The completed grid gives the sum: 129 + 438 = 567.

The first 10 odd numbers sum to 100 (1 + 3 + 5 + 7 + 9 + 11 + 13 + 15 + 17 + 19 = 100), so there must be fewer than 10 “other” Hills. Also the sum of the ages of “other” Hills must give an even number, so there must an even number of “other” Hills. We are told the “other” hills consist of Mrs Hill and some children (plural), so there are at least 3 “other” Hills. The only possible numbers of “other” hills are: 4, 6, or 8.

Looking at the prime factorisations of the corresponding repunits:

R(4) = 1111 = 11 × 101

R(6) = 111111 = 3 × 7 × 11 × 13 × 37

R(8) = 11111111 = 11 × 73 × 101 × 137

Each prime factor must appear as a factor of one of the “other” Hills ages, so any repunit with a prime factor greater than 100 is out, leaving *R(6)* as the only viable solution. And we must split *R(6)* into 6 factors. As it has 5 different prime factors the solution must be those along with an additional factor of 1.

This gives the ages of the 6 “other” Hills as: 1, 3, 7, 11, 13, 37, and Mr Hill’s age is the sum of these, 72.

]]>This Python 3 code runs in 124ms.

**Run:** [ @repl.it ]

from enigma import irange, update, ordered, chunk, printf # label the squares: # # 0 1 2 3 # 4 5 6 7 # 8 9 10 11 squares = set(irange(0, 11)) # then the corresponding move functions are def move(k, i): (r, c) = k moves = { # 0: "go down one" 0: (r + 1, c), # 1: "start here, and go right one" 1: (r, c + 1), # 2: "go diagonally one, down + right" 2: (r + 1, c + 1), # 3: "go down two if possible: if not, go up one" 3: (r + 2, c) if r < 2 else (r - 1, c), # 4: "go right two" 4: (r, c + 2), # 5: "go diagonally one, down + left" 5: (r + 1, c - 1), # 6: "go diagonally one, up + right" 6: (r - 1, c + 1), # 7: "go left two" 7: (r, c - 2), # 8: "go up two" 8: (r - 2, c), # 9: "go right one" 9: (r, c + 1), # 10: "finish here", so we don't need it # 11: "go left two" 11: (r, c - 2) } (r1, c1) = moves.get(i, (0, 0)) assert 0 < r1 < 4 and 0 < c1 < 5 return (r1, c1) def solve(g, k, squares): # are we done? if not squares: yield (g, k) # choose a square to place at (r, c) for i in squares: try: k2 = move(k, i) except AssertionError: continue if k2 not in g: yield from solve(update(g, [(k, i)]), k2, squares.difference([i])) # choose a value from dict <d> def choose(d): for x in d.items(): return x # find value <v> in dict <d> def find(d, v): for (k, v1) in d.items(): if v == v1: return k # remove items from dict <d> def remove(d, ks): d = d.copy() for k in ks: del d[k] return d # can g1 and g2 be made from the same set of dominoes? def dominoes(g1, g2, ds=set()): # are we done? if not g1: if not g2: yield sorted(ds) else: # choose a square from g1 ((r1, c1), s1) = choose(g1) # and consider adjacent squares for (r2, c2) in ((r1 - 1, c1), (r1 + 1, c1), (r1, c1 - 1), (r1, c1 + 1)): s2 = g1.get((r2, c2)) if s2 is None: continue # are they adjacent squares in g2? ((r1x, c1x), (r2x, c2x)) = (find(g2, s1), find(g2, s2)) (r, c) = (abs(r1x - r2x), abs(c1x - c2x)) if (r == 0 and c == 1) or (c == 0 and r == 1): # remove the domino from each grid, and solve the remaining squares yield from dominoes(remove(g1, [(r1, c1), (r2, c2)]), remove(g2, [(r1x, c1x), (r2x, c2x)]), ds.union([ordered(s1, s2)])) # the diagram grid g0 = dict() for i in squares: (r, c) = (x + 1 for x in divmod(i, 4)) g0[(r, c)] = i # set up the new grid (k, i) = ((2, 2), 1) grid = { k: i } # place all the squares (except the final square) in the grid for (g, k) in solve(grid, move(k, i), squares.difference([i, 10])): # place the final square g = update(g, [(k, 10)]) # try to match the grids dominowise for ds in dominoes(g0, g): printf("top left = {tl}, bottom right = {br}", tl=g[(1, 1)], br=g[(3, 4)]) printf(" grid = {g}", g=list(chunk((g[(r, c)] for r in irange(1, 3) for c in irange(1, 4)), 4))) printf(" dominoes = {ds}")

**Solution:** In the new grid the instruction in the top left-hand corner is “GO RIGHT TWO”, and the instruction in the bottom right-hand corner is “GO LEFT TWO”.

There is only one way to make up the grid so that you can start in row 2, column 2 and complete the grid, even if you cut the original grid into single squares, so we know what the solution is, if it is possible.

There are two tiles that read “GO LEFT TWO”, so these tiles can be interchanged without changing the pattern. But by cutting the original grid into 2×1 “dominoes” only one of these tiles can appear in the bottom right-hand square, but there are multiple ways to make the cuts. We can cut the square into 2 2×1 rectangles and 2 2×2 rectangles which re-arrange to make the solution grid, as shown below:

The 2×2 tiles can be cut either horizontally or vertically to make dominoes, which gives us 4 different sets of dominoes that can be arranged to make the grid.

]]>