#!/usr/bin/python3 from functools import lru_cache def A114531(n): rv = 1; # for the empty subset for w in range(1, n+1): for h in range(w, n+1): # How many w x h boxes are there in the n x n grid? weight = (n+1 - w) * (n+1 - h) # Plus if w != h we count h x w boxes. if w != h: weight *= 2 rv += weight * count_bbox(w, h) return rv @lru_cache(maxsize=None) def count_bbox(w, h): # If either dimension is 1 then the convex hull is precisely the bounding box. if w == 1 or h == 1: return 1 # We have two corners of the convex hull in the top row, although they might not be distinct. # Similarly the bottom row, left column, and right column. # Thus we get a (possibly degenerate) octagon. # The contents of the octagon are in the convex hull, so those points are irrelevant. # The points outside the octagon split into four independent triangles, which can be clustered into two independent halves to optimise for speed. bboxCount = 0; for t0 in range(0, w): for b0 in range(0, w): for t1 in range(0, w-t0): for b1 in range(0, w-b0): bboxCount += count_half(t0, b0, h) * count_half(t1, b1, h) return bboxCount @lru_cache(maxsize=None) def count_half(w0, w1, h): if (w0 > w1): return count_half(w1, w0, h) halfCount = 0; for l0 in (range(0, 1) if w0 == 0 else range(1, h)): for l1 in (range(0, 1) if w1 == 0 else range(1, h-l0)): halfCount += count_corner(w0, l0) * count_corner(w1, l1) return halfCount @lru_cache(maxsize=None) def count_corner(w, h): if w > h: return count_corner(h, w) if w == 0: return 1 if h == 0 else 0 return count_corner_inner(None, (0, h), (w, 0), 1) @lru_cache(maxsize=None) def count_corner_inner(penultimate, prev, final, x): # Count distinct hulls using lattice points above the line penultimate -- prev, # below the line prev -- final, and with x-coordinate >= x if x == final[0]: return 1 if penultimate == None: y_min = 1 else: md_min = prev[0] - penultimate[0] # Add one and floor to ensure that we don't have colinear points y_min = penultimate[1] + (prev[1] - penultimate[1]) * (x - penultimate[0]) // md_min + 1 if y_min < 1: y_min = 1 md_max = final[0] - prev[0] # Floor, decrementing if necessary to ensure we don't have colinear points y_max = prev[1] + ((final[1] - prev[1]) * (x - prev[0]) - 1) // md_max result = sum(count_corner_inner(prev, (x, y), final, x+1) for y in range(y_min, y_max+1)) # But maybe we don't have anything in column x return result + count_corner_inner(penultimate, prev, final, x+1) if __name__ == "__main__": print([A114531(n) for n in range(24)])