import numpy as np
from typing import List, Set, Tuple, Union
def _make_rng(rng: Union[None, int, np.random.Generator]) -> np.random.Generator:
"""Return a deterministic Generator from seed/int/Generator."""
if isinstance(rng, np.random.Generator):
return rng
return np.random.default_rng(0 if rng is None else int(rng))
def _has_no_digit_two(x: int) -> bool:
"""True iff the base‑3 representation of x contains no digit 2."""
while x:
if x % 3 == 2:
return False
x //= 3
return True
def _conflict_sets(val: int, cur: Set[int]) -> Tuple[Set[int], Set[int]]:
"""
Return two sets of conflicts with `val` against the current set `cur`:
outer – members that together with `val` would be the two outer terms
of a 3‑AP whose middle is already in `cur`.
middle – members that would be the middle term of a 3‑AP with `val`
as one outer term.
"""
outer = set()
middle = set()
for y in cur:
s = val + y
if s % 2 == 0 and (s // 2) in cur:
outer.add(y) # y is the other outer term
if (2 * val - y) in cur:
middle.add(y) # y is the other outer term, val middle
if (2 * y - val) in cur:
outer.add(y) # y is middle, val outer
return outer, middle
def _weighted_score(val: int, cur: Set[int],
w_outer: int = 1, w_mid: int = 2) -> int:
"""Score = w_outer·|outer| + w_mid·|middle|; lower is better."""
outer, middle = _conflict_sets(val, cur)
return w_outer * len(outer) + w_mid * len(middle)
def heuristic(n: int,
rng: Union[None, int, np.random.Generator] = None) -> List[int]:
"""
Build a large 3‑AP‑free subset of {1,…,n}.
1. Initialise with the “Cantor” set (no ternary digit 2).
2. Perform a bounded local search that swaps out up to three conflicting
elements for a missing element, using a score that penalises middle‑role
conflicts twice as much as outer‑role conflicts.
Returns the final set as a sorted list.
"""
if n <= 0:
return []
rng = _make_rng(rng)
# -----------------------------------------------------------------
# 1. Base Cantor construction
# -----------------------------------------------------------------
S: Set[int] = {i for i in range(1, n + 1) if _has_no_digit_two(i)}
missing: List[int] = [i for i in range(1, n + 1) if i not in S]
rng.shuffle(missing) # deterministic tie‑breaker
# -----------------------------------------------------------------
# 2. Local improvement (swap ≤3 conflicting members for one missing)
# -----------------------------------------------------------------
budget = int(4 * np.sqrt(n)) + 200 # number of attempts
attempts = 0
idx = 0
while attempts < budget and missing:
x = missing[idx % len(missing)]
idx += 1
attempts += 1
# compute conflicts of x with current set
outer, middle = _conflict_sets(x, S)
all_conf = outer | middle
if not all_conf:
# x fits for free – just add it
S.add(x)
missing.remove(x)
attempts = 0
continue
# limit to at most three conflicting elements; otherwise skip
if len(all_conf) > 3:
continue
# enumerate all non‑empty subsets of size ≤3 of the conflicting set
conf_list = list(all_conf)
best_combo = None
for r in range(1, min(3, len(conf_list)) + 1):
# small numbers → simple bit‑mask enumeration
for mask in range(1 << len(conf_list)):
if bin(mask).count("1") != r:
continue
combo = tuple(conf_list[i] for i in range(len(conf_list))
if (mask >> i) & 1)
temp = S.difference(combo)
if not _conflict_sets(x, temp)[0] and not _conflict_sets(x, temp)[1]:
best_combo = combo
break
if best_combo is not None:
break
if best_combo is None:
continue
# perform the swap
for y in best_combo:
S.remove(y)
missing.append(y)
S.add(x)
missing.remove(x)
attempts = 0
# after a successful swap, greedily insert any newly free numbers
filler = True
while filler:
filler = False
for y in list(missing):
if not _conflict_sets(y, S)[0] and not _conflict_sets(y, S)[1]:
S.add(y)
missing.remove(y)
filler = True
attempts = 0
# final deterministic ordering
return S
x = heuristic(82)
print(x)
print(len(x))