We shall define a square lamina to be a square outline with a square “hole” so that the shape possesses vertical and horizontal symmetry.
Given eight tiles it is possible to form a lamina in only one way: 3 × 3 square with a 1 × 1 hole in the middle. However, using thirty-two tiles it is possible to form two distinct laminae. If t represents the number of tiles used, we shall say that t = 8 is type L(1) and t = 32 is type L(2).
Let N(n) be the number of t <= 1000000 such that t is type L(n); for example, N(15) = 832.
What is sum of N(n) for n=1 to 10?
This question turned out to be fairly straight forward, and is solvable using a brute force method.
Basically, we want to arrange tiles in laminae that are symmetrical, and don’t use more than 1,000,000 tiles. The largest lamina we can arrange would then be 250,000 tiles per side, with a wall thickness of 1.
The way I set this up was to iterate through all lamina side lengths (L) from 3 to our limit, 250,000. Then, for each, iterate for different hole sizes (H) in the middle from L–2 down to 1. (The evenness of L and H have to match.)
Calculating the number of tiles is an easy prospect; t = L² – H². A dictionary then keeps track of how many tiles are used for each combination. Once we’ve brute forced all the combinations, look through the dictionary to count up all the tile values that have 10 solutions or less!
let history = {};
let limit = 1000000;
for (let L=3; L<250000; L++){
let L2 = L * L;
// Hole (H) size must be at least 1, smaller than or equal to l-2
for (let H=0; H<=L-2; H++){
// Calculate number of tiles (t) in lamina
let t = L2 - (H * H);
// L and H evenness must match
if (L%2 != H%2) continue;
// Ignore answers over limit
if (t > limit) continue;
// Store result in history
if (history[t] == undefined)
history[t] = [];
history[t].push(L);
}
}
let count = 0
for (let i in history)
if (history[i].length <= 10)
count++;
console.log(count);
The first time I ran this, it was much slower than anticipated. I let it run overnight, and was happy to see the correct result when I checked on it the next day. I wondered what I needed to optimize to make it run better. It turns out that I had typed the upper limit, L, as 2,500,000! By decreasing that to the proper value, that reduced the compute time to 17 seconds.
I was able to find one more low-hanging optimization opportunity. By iterating through the H values from largest to smallest, the number of tiles, t, would increase. Once that value got over the 1,000,000 limit, I can break out of the loop, and go to the next value of L. This saves a lot of time computing out of bounds answers!
for (let L=3; L<250000; L++){
let L2 = L * L;
// Hole (H) size must be at least 1, smaller than or equal to l-2
for (let H=L-2; H>1; H-=2){
// Calculate number of tiles (t) in lamina
let t = L2 - (H * H);
// Break once we get over limit
if (t > limit) break;
// Store result in history
if (history[t] == undefined)
history[t] = [];
history[t].push(L);
}
}
This optimization pushed the compute time down to .278s! That’s a very reasonable time for Project Euler.