The numbers are too big to post, so here they are on Pastebin: num 1, num 2.
The first number is 600^2 = 360000
ones. The second number is the same, except for the following changes:
Positions to change to "2": 605, 1811, 3001, 6603
Positions to change to "4": 1805, 3003, 57348, 208895
Positions to change to "5": 602, 1201, 2405, 3004
Positions to change to "6": 1203, 1802
Positions to change to "7": 12, 609, 5401, 7200
Positions to change to "8": 1, 2, 4, 6, 600, 1200, 1808, 2400, 3600, 4803
Both hash to 271088937720654725553339294593617693056
.
Explanation
Let's take a look at the first half of the code:
lW% e# Read input number as string, and reverse
600/ e# Split every 600 digits, forming a 2D array
_z e# Duplicate and zip, swapping rows and columns
{ }% e# For both arrays...
JfbDb e# Find sum of S[i][j]*13^i*19^j, where S are the character values
e# and the indices are from right to left, starting at 0.
GK# e# Take modulo 16^20
... ... e# (Rest of code irrelevant)
So if we can find two input numbers so that the sums of S[i][j]*13^i*19^j
are the same modulo 16^20
for both the initial 600-wide array and the zipped array, then we're done.
To make things a little easier, we'll consider only 600^2 = 360000
-digit input numbers, so that the 600-wide array is just a 600 by 600 square of digits. This makes things easier to visualise, and is valid since 10^360000 ~ 2^(2^20.19) < 2^(2^30)
. To simplify things even further, we'll only consider such input strings whose digit square is symmetric along the main diagonal, so that the original array and the zipped array are the same. This also allows us to ignore the initial string reversal and the right-to-left index numbering, which cancel each other out.
To start us off, we can take the first number to be 360000
ones. To get the second number, we want to modify this by changing some of the digits so that the sums are the same modulo 16^20
, while preserving the digit square's symmetry. We accomplish this by finding a list of triples (i, j, k)
so that
sum of k*(13^i 19^j + 19^i 13^j) == 0 mod 16^20
where 1 <= k <= 8
is the amount to increase the digit 1 by (i.e. changing to a digit from 2 to 9 – we could have included 0 but we didn't need it) and 0 <= i < j < 600
are index pairs.
Once we have the (i, j, k)
triplets, we change the digits at (i, j)
and (j, i)
to 1+k
to get the second number. The triplets were found using a greedy backtracking algorithm, and for the second number above the digit square looks like:
188181811111711 ...
815112111711111 ...
851611111111111 ...
116114118112111 ...
811115111111111 ...
121451111111111 ...
811111111111111 ...
111111111111111 ...
111811111111111 ...
171111111111111 ...
111111111111111 ...
111211111111111 ...
711111111111111 ...
111111111111111 ...
111111111111111 ...
............... .
............... .
............... .
For example, (i, j, k) = (0, 1, 7)
corresponds to changing the digits (0, 1)
(position 600*0 + 1 = 1
) and (1, 0)
(position 600*1 + 0 = 600
) to 1 + 7 = 8
.
Here's the backtracker in Python 3, although closer inspection revealed that we were pretty lucky, as no backtracking actually happened:
n = 16**20
L = [(k *(pow(13,i,n)*pow(19,j,n) + pow(19,i,n)*pow(13,j,n)) % n, i, j, k)
for i in range(600) for j in range(600) for k in range(1, 9) if i < j]
L.sort(reverse=True)
stack = [(n, 0, [])]
while stack:
k, index, result = stack.pop()
if k == 0:
print(result)
break
if index == len(L):
continue
stack.append((k, index+1, result)) # Don't include triplet
if L[index][0] <= k:
stack.append((k - L[index][0], index+1, result + [L[index][1:]])) # Include
For a bonus, here's a not-so-efficient port of the hash in Python 3. It was useless.
Awesome! I'm actually flattered in an odd sort of way! :D – Mr. Llama – 2015-06-05T20:06:58.003
Also, for my own personal education, when you say the higher order bits never affect the lower ones, what do you mean? The higher order bits of the input string, or of the hash state? – Mr. Llama – 2015-06-05T20:13:16.333
@Mr.Llama In the hash state, the upper bits of x and y will never influence the lower bits, so for instance if you flip on of the middle bits during computation the low part of the hash will still come out right. This lets me start out ignoring everything but the lowest bits of the hash state, then when I have those under complete control I move on to the next layer of bits, and so forth. – aaaaaaaaaaaa – 2015-06-05T20:36:39.440
Cool! Thanks for the explanation! – Mr. Llama – 2015-06-05T20:44:32.857
Congratulations on winning the robbers challenge! – Dennis – 2015-06-10T20:19:00.357