1.

pqis divisible by 2;

2.p≥ 5 andq≥ 5;

3. (p,q) ≠ (6, 6).

The proof that the 6×6 grid is not fault-free is nice.

This means that all even area grids that have both dimensions greater that 4 have a fault-free tiling, apart from the 6×6 grid.

The following program gives an analytical solution to the puzzle in 40ms, which is much less work than the constructive solution.

from enigma import irange, divisor_pairs, printf # find rectangles made from n dominoes that are fault-free ff = list() for n in irange(2, 28): grids = list((p, q) for (p, q) in divisor_pairs(2 * n) if p > 4 and q > 4 and not(p == q == 6)) if grids: printf("[fault-free = {n} dominoes, grids = {grids}]") ff.append(n) # find sets of dominoes which differ by 2 for n in sorted(ff): if n - 2 in ff: printf("SOLUTION: rectangle 1 = {m} dominoes, rectangle 2 = {n} dominoes", m=n - 2)]]>

from itertools import combinations from collections import namedtuple from enigma import irange, diff, filter_unique, printf # possible winning scores in a set sets = set((6, x) for x in irange(0, 4)).union([(7, 5), (7, 6)]) # accumulate possible scores for A and B (and number of games won) Result = namedtuple('Result', 'g A B') r = list() # A won 3 sets with different scores for A in combinations(sets, 3): # count the number of games A and B won (gA1, gB1) = (sum(s) for s in zip(*A)) # consider sets that B won for B in combinations(sets.difference(A).union([(0, 0)]), 2): (gB2, gA2) = (sum(s) for s in zip(*B)) g = gA1 + gA2 if g == gB1 + gB2: B = diff(B, [(0, 0)]) r.append(Result(g, A, B)) printf("games = {g} [A = {A}, B = {B}]") # find solutions, unique by the number of games (r, _) = filter_unique(r, (lambda t: t.g)) for (g, A, B) in r: printf("A = {A}, B = {B}, games = {g}", A=sorted(A), B=sorted(B))

**Solution:** Andre won his sets 6-3, 6-4, 7-6.

Andre won 3 sets to 1. The score in the set Boris won was 6-0. So overall each player won 19 games.

]]>from itertools import combinations from enigma import irange, printf # the lists of guests guests = { 1: 'ABCEFHIJK', 2: 'BCEHIK', 3: 'ACDEFGHIJL', 4: 'CI', 5: 'BCDEGHIK', 6: 'BCIK', 7: 'ABCDFGIJKL', 8: 'ABCEFHIJKL', 9: 'ABDEFGHJKL', 10: 'BCDEGHIJKL', } # turn the guest lists into sets for (k, v) in guests.items(): guests[k] = set(v) # consider subsets of size k for k in irange(2, 10): ss = list() for s in combinations(guests.keys(), k): # find guests that are in all lists gs = set.intersection(*(guests[i] for i in s)) if len(gs) == 1: printf("[{k}: {s} -> {gs}]") ss.append((s, gs.pop())) if ss: for (s, g) in ss: printf("lists = {s}, robber = {g}") break

**Solution:** The puzzle should use lists 1, 3, 7, 9, 10. The answer to the puzzle is that Jack is the robber.

The solution uses 5 of the lists, but we can also include list 8. But these are the only subsets that uniquely identify a single guest.

]]>% A Solution in MiniZinc include "globals.mzn"; % G H I % --------- % E F ) A B C D % J K % --- % L M C % N O P % ----- % Q D % Q D % === var 0..9:A; var 0..9:B; var 0..9:C; var 0..9:D; var 0..9:E; var 0..9:F; var 0..9:G; var 0..9:H; var 0..9:I; var 0..9:J; var 0..9:K; var 0..9:L; var 0..9:M; var 0..9:N; var 0..9:O; var 0..9:P; var 0..9:Q; % Digits which cannot be zero constraint A != 0 /\ E != 0 /\ G != 0 /\ J != 0 /\ L != 0 /\ N != 0 /\ Q != 0; % Invalid digits given in the puzzle constraint G != 1 /\ L != 1 /\ E != 2 /\ N != 2 /\ J != 6; % Variables used in the division puzzle var 10..99: EF = 10*E + F; var 10..99: JK = 10*J + K; var 10..99: QD = 10*Q + D; var 10..99: LM = 10*L + M; var 10..99: AB = 10*A + B; var 100..999: GHI = 100*G + 10*H + I; var 100..999: LMC = 100*L + 10*M + C; var 100..999: NOP = 100*N + 10*O + P; var 1000..9999: ABCD = 1000*A + 100*B + 10*C + D; % the main division sum constraint GHI * EF == ABCD; % multiplication constraints constraint G * EF == JK /\ H * EF == NOP /\ I * EF == QD; % intermediate subtraction sum constraints constraint AB - JK == LM /\ LMC - NOP == Q; solve satisfy; output [ show(ABCD) ++ " / " ++ show(EF) ++ " = " ++ show(GHI) ++ "\n" ++ "JK = " ++ show(JK) ++ " LMC = " ++ show(LMC) ++ " NOP = " ++ show(NOP) ++ " NOP = " ++ show(NOP) ++ " QD = " ++ show(QD) ]; % 9828 / 39 = 252 % JK = 78 LMC = 202 NOP = 195 NOP = 195 QD = 78 % Finished in 133msec % % 252 % ------ % 39) 9828 % 78 % --- % 202 % 195 % --- % 78 % 78 % ==]]>

We represent the parts of the diagram as a number of alphametic expressions, and restrict the letters that have digits given from taken on those values.

This run file executes in 103ms.

#!/usr/bin/env python -m enigma -r # consider the division: # # G H I # --------- # E F ) A B C D # J K # --- # L M C # N O P # ----- # Q D # Q D # === # solver to use SubstitutedExpression # solver parameters --distinct="" --invalid="0,AEGJLNQ" --invalid="1,GL" --invalid="2,EN" --invalid="6,J" # the main division sum "GHI * EF = ABCD" # the multiples "G * EF = JK" "H * EF = NOP" "I * EF = QD" # the intermediate subtraction sums "AB - JK = LM" "LMC - NOP = Q"

**Solution:** The correct sum is: 9828 ÷ 39 = 252.

Since **Enigma 389** I’ve been thinking about replacing the `SubstitutedDivision()` solver (originally written for **Enigma 206**, but used to provide solutions for many other substituted division problems – until I wrote the `SubstitutedExpression()` solver) with a solver that will turn a long division problem into a set of expressions suitable for feeding to the `SubstitutedExpression()` solver. I’ve generally found that using `SubstitutedExpression()` produces code that executes faster than the `SubstitutedDivision()` solver, and using the `SubstitutedExpression()` solver has a lot more bells and whistles than the `SubstitutedDivision()` solver.

I currently have the new `SubstitutedDivision()` solver in testing and will roll it out to **enigma.py** shortly. While it will handle most `SubstitutedDivision()` problems in the same way, there are some cases where a different approach is used with the new solver. Once the new solver is launched I will check my solutions I have previously posted using the old solver and add revised code if necessary.

There’s a couple of mechanisms for generating co-prime pairs given in **Enigma 1182**, which Ben could use to generate co-prime pairs less than 100,000. (These are available as the functions `farey()` and `coprime_pairs()` in the **enigma.py** library).

For sets of 4 points, Ben can use the corners of any unit square (there are quite a lot of those), or he could use a “skew square” with corners at *(0, 1), (1, k+2), (k+2, k+1), (k+1, 0)*. The distances between the co-ordinates of adjacent corners are always 1 and *k+1* (which are necessarily co-prime), and the distances between the co-ordinates of the diagonals are *k* and *k+2*. So as long as *k* and *k+2* co-prime that would work. Armed with a list of twin primes [ OEIS A077800 ], Ben could make quite a lot of 4-sets.

Is there a rule that Ben can follow to choose four points (or three, or just two) such that there can be no intermediate grid points? Will he have to take their coordinates modulo every prime number in the book, to cover all parities besides the normal twofold parity?

]]>The two points you give have different pairs of residues modulo 2 (so there is no intermediate point halfway between them), but the distances between their respective *x* and *y* co-ordinates have a GCD of 3, so the line joining them is split into three segments and there are two intermediate points at distances 1/3 and 2/3 between them. The points have the same pair of residues modulo 3.

You can follow the same argument I give above to show that in any set of 3×3 + 1 = 10 points there will always be a pair of points that are joined by a line segment that is split into thirds by intermediate points. (We can chose the 9 points given by (0, 1, 2) × (0, 1, 2) to find a set of 9 points that don’t have this property).

But we already know from considering residues modulo 2 that we can find a pair where the line segment is split in half by intermediate points in a smaller set of points.

The reasoning implies you won’t be able to find three other points to go with (0, 0) and (3, 18) to make a set that does not contain a pair with a line segment that is split in half by an intermediate point.

]]>Incidentally, red dots in squares is an unnecessary complication. They play the game on a square grid; they can reserve the red pen for marking the grid intersections to be joined.

And I think they’re soon going to tire of their game!

We are trying to find the smallest number of points *k* such that given *any* set of *k* points, we can always find a pair of points such that the line segment between these two points passes through an intermediate point.

We can easily choose up to 4 points, such that the line segment formed by any pair does not pass through an intermediate point. We just need to choose the points from the vertices of the unit square: (0, 0), (0, 1), (1, 0), (1, 1). But there are other solutions with four points, e.g. (0, 0), (2, 3), (5, 7), (7, 10).

If we consider the parities of the co-ordinates of the points, using their residues modulo 2, we get one of the following values for each point: (0, 0), (0, 1), (1, 0), (1, 1). (These are the vertices of the unit square again).

And if we try to add a further point to the set, the parities of its co-ordinates will have one of these 4 values. So in any set of 5 (or more) points there will be at least two points that have the same parity pairing.

Consider two different points with the same parity pairing. The parity is *(p, q)* and the points are *(2a + p, 2b + q)* and *(2c + p, 2d + q)*. Where *p, q* are 0 or 1 and *a, b, c, d* are integers. Then the mid-point of the line segment between these two points is:

((2a + p + 2c + p) / 2, (2b + q + 2d + q) / 2) = (a + c + p, b + d + q)

Which is a lattice point.

So in *any* set of 5 points there will always be two points such that the line segment joining those points passes through an intermediate point.

**Solution:** The smallest number Matthew can choose and be certain of winning is 5.

In general the number of intermediate points on the line segment between the points *(a, b)* and *(c, d)* is given by:

gcd(|a – c|, |b – d|) – 1

(See: **Enigma 1308**).

So, there will be no intermediate points if:

gcd(|a – c|, |b – d|) = 1

The following Python program exhaustively looks for *k*-sets of dots on the grid with no pair that form a line segment that passes through an intermediate point.

from enigma import irange, gcd, arg, printf # there are 100000 centimetres in a kilometre k = arg(5, 0, int) N = arg(100000, 1, int) printf("[N={N}, k={k}]") # max index M = (N ** 2) - 1 # choose <k> points (from index <i> onwards) that form a set with <ps> def solve(k, ps=[], i=0): if k == 0: return ps else: # choose the next point for j in irange(i, M): (y, x) = divmod(j, N) if all(gcd(abs(x - a), abs(y - b)) == 1 for (a, b) in ps): return solve(k - 1, ps + [(x, y)], j + 1) return None # start with (0, 0) ps = solve(k - 1, [(0, 0)], 1) printf("[N={N}] {k} => {ps}")

With the default settings (k=5, and a 100,000 × 100,000 grid) the program takes 1h50m to confirm that it is not possible to find a suitable set.

But the parameters can be specified on the command line, and 5-sets on a 1,000 × 1,000 grid can be checked in under a second.

]]>This Python 3 program considers the three numbers by looking at increasing values for the total sum, and then breaking down that value into three different numbers, until it finds a set that satisfies the conditions.

It runs in 66ms.

from itertools import count, combinations from enigma import irange, flatten, printf # decompose the total <t> into <n> different numbers def decompose(t, n, s=[]): if n == 1: if not(s) or t > s[-1]: yield s + [t] elif n > 1: for x in irange((s[-1] + 1 if s else 1), t // n): yield from decompose(t - x, n - 1, s + [x]) # consider possible total sum of all three batteries for t in count(6): for (a, b, c) in decompose(t, 3): # make the list of sums and differences s = flatten((x + y, y - x) for (x, y) in combinations((a, b, c), 2)) ss = sorted(set(s)) # the second smallest reading is 13 or 17, and occurs twice if not((ss[1] == 13 or ss[1] == 17) and s.count(ss[1]) == 2) : continue # and the reverse also occurs if not((ss[1] == 13 and 31 in s) or (ss[1] == 17 and 71 in s)): continue printf("a={a} b={b} c={c}, s={s}", s=sorted(s)) exit()

**Solution:** The three batteries are 4V, 9V and 22V.

The fact that the doubled reading occurs in the list with its digits reversed is not inconsequential. Without this fact there are 14 solutions.

]]>from enigma import filter_unique, printf # evaluate the statements # return the number of true statements def statements(culprit): # the statements ss = ( # D says it was B or C (culprit == 'B' or culprit == 'C'), # A says it was B (culprit == 'B'), # B says it was C (culprit == 'C'), # C says it was D (culprit == 'D'), # E says it was not C (culprit != 'C'), ) # how many are true? n = ss.count(True) printf("culprit = {culprit} {ss}, true = {n}") return n # find culprits with a unique statement count (culprits, _) = filter_unique('ABCDE', statements) printf("culprits = {culprits}")

**Solution:** Bert let the cat out (shame on him!)

I assumed that one of the named people was the culprit, but we could add an extra symbol to the code to account for the case of an unnamed culprit, although as no-one mentions E in their statements the results for the unnamed culprit will be the same as for E.

]]>It is not very pretty code and I was not sure whether to post it, but it gets the answer in about 100 msec on my laptop. Fortunately, clue 3 down gives a unique answer for the number base, which is used for the rest of the code.

# Grid for unknown digits # A B A # 1 0 0 # C D A def conv_base(b,A,B,C): # base b,3 digits are A, B and C - converts base b to base 10 if A > 0 and A < b: if B < b and C < b: num = A*b*b + B*b + C if 2 <= num < 1000: return num def is_prime(n): for x in range(2, int(n**0.5)+1): if n % x == 0: return False return True # list of primes less than 1000 pr_list = [] for n in range(2,1000): if is_prime(n): pr_list.append(n) # clue 3 down - A0A - to find A and base b for n in (101, 202, 303, 404, 505, 606, 707, 808, 909): for b in range(3,19): num = conv_base(b, n // 100, n // 10 % 10, n % 10) for a in range(1,33): if a * a == num: A = n // 100 print('A = ', n // 100, 'base = ',b) # A = 2, b = 7 base = b # use for other clues # clue 1 across - ABA - to find B for n in (212, 222, 232, 242, 252, 262, 272, 282, 292): num = conv_base(base, A, n// 10 % 10, A) if num in pr_list: B = int(n // 10 % 10) print('B = ',B) # B = 1 # clue 2 down - B0D - to find D for n2 in (101, 102, 103, 104, 105, 106, 107, 108, 109): num2 = conv_base(base, B, 0, n2 % 10) if num2 in pr_list: D = int(n2 % 10) print('D = ', D ) # D = 4 # clue 5 across - CDA - to find C for n5 in (142, 242, 342, 442, 542, 642, 742, 842, 942): num5 = conv_base(base, n5 //100, D, A) for n in range(10,33): if n * n == num5: C = n5 // 100 print('C = ', C) # C = 6 # Check 1 down - my son's age (A1C) # A1C = 216 num6 = conv_base(7,2,1,6) print( 'My son''s age = ',num6 // 3) print() print ('Grid is:') print(A,B,A) print(1,0,0) print(C,D,A) ''' Output A = 2 base = 7 B = 1 D = 4 C = 6 My sons age = 37 Grid is: 2 1 2 1 0 0 6 4 2 ''']]>

We are also told that 1 across and 3 down are palindromic numbers, so there are only 4 unknown values to place in the squares.

Additionally the smallest possible value for 1 down is 111. In base 18 this corresponds to 343 (decimal), which would make the son at least 114, so I don’t bother checking bases higher than this.

This Python program considers bases between 2 and 18 and creates a collection of alphametic expressions in that base, then uses the generalised alphametic solver (`SubstituedExpression()`) from the **enigma.py** library to look for solutions.

It runs in 66ms

# 1 across is palindromic # 4 across is 100 # 3 down is palindromic # # using Z=0, U=1, the square is: # # A B A # U Z Z # C D A from enigma import SubstitutedExpression, irange, nconcat, printf # the remaining expressions exprs = [ # "1 across: A palindromic prime" "is_prime(ABA)", # "5 across: A square" "is_square(CDA)", # "1 down: 3 times my son's age" "AUC % 3 = 0", # "2 down: A prime" "is_prime(BZD)", # "3 down: A palindromic square" "is_square(AZA)", ] # consider bases up to 18 # base2int("100", 18) = 324, so the son would be at least 108 for b in irange(2, 18): # create the alphametic problem in the appropriate base p = SubstitutedExpression( exprs, symbols="ABCDUZ", distinct="", l2d={ 'Z': 0, 'U': 1 }, base=b, ) # and solve it for s in p.solve(verbose=0): (A, B, C, D, U, Z) = (s[x] for x in 'ABCDUZ') # calculate the sons age (= AUC // 3) age = nconcat(A, U, C, base=b) // 3 printf("age={age}, base={b}, square=[{A} {B} {A} / {U} {Z} {Z} / {C} {D} {A}]")

**Solution:** Your son is 37.

The cross-figure is in base 7, and the filled out grid is:

2 1 2 1 0 0 6 4 2

1 across: 212 (base 7) = 107 (decimal), prime.

4 across: 100 (base 7) = 49 (decimal), 7².

5 across: 642 (base 7) = 324 (decimal), 18².

1 down: 216 (base 7) = 111 (decimal), 3× 37.

2 down: 104 (base 7) = 53 (decimal), prime.

3 down: 202 (base 7) = 100 (decimal), 10².