import numpy as np from typing import List, Set, Union def _make_rng(rng: Union[None, int, np.random.Generator]) -> np.random.Generator: '''Create a deterministic RNG from None, an int seed or a Generator.''' if rng is None: return np.random.default_rng(0) if isinstance(rng, np.random.Generator): # copy state to avoid side‑effects state = rng.bit_generator.state return np.random.Generator(np.random.PCG64(state)) return np.random.default_rng(int(rng)) def _conflicts(x: int, S: Set[int]) -> bool: '''True iff adding x to S creates a 3‑AP with two elements already in S.''' for y in S: # x as middle term if (x + y) % 2 == 0 and ((x + y) // 2) in S: return True # x as an endpoint if (2 * x - y) in S or (2 * y - x) in S: return True return False def heuristic(n: int, rng: Union[None, int, np.random.Generator] = None) -> List[int]: ''' Return a large 3‑AP‑free subset of {1,…,n}. The algorithm performs a greedy randomized construction and a short random‑swap local improvement phase. ''' if n <= 0: return [] gen = _make_rng(rng) # ---------- 1. Greedy randomized construction ---------- all_numbers = np.arange(1, n + 1, dtype=int) gen.shuffle(all_numbers) # random order, deterministic S: Set[int] = set() omitted: Set[int] = set() for x in all_numbers: if not _conflicts(int(x), S): S.add(int(x)) else: omitted.add(int(x)) # ---------- 2. Local improvement (random swaps) ---------- # Budget proportional to sqrt(n) * n to stay fast for a few thousand. budget = int(3 * n * np.sqrt(n)) temperature_start = 1.0 temperature_end = 0.001 omitted = set(range(1, n + 1)) - S # recompute after greedy phase for step in range(budget): # linear cooling T = temperature_start + (temperature_end - temperature_start) * (step / budget) if not omitted: break # pick a random element not in S x = int(gen.choice(list(omitted))) # collect elements of S that would conflict with x conflicting: List[int] = [] for y in S: if (x + y) % 2 == 0 and ((x + y) // 2) in S: conflicting.append(y) elif (2 * x - y) in S or (2 * y - x) in S: conflicting.append(y) # If no conflict we could simply add, but that was already checked. # Try a replace‑one‑with‑up‑to‑two move. if not conflicting: continue # remove one conflicting element (randomly) to_remove = int(gen.choice(conflicting)) S.remove(to_remove) omitted.add(to_remove) # attempt to insert x and possibly another non‑conflicting omitted element added: List[int] = [] candidates = [x] + list(omitted) gen.shuffle(candidates) for y in candidates: if y == to_remove: continue if not _conflicts(y, S): S.add(y) omitted.remove(y) added.append(y) if len(added) == 2: break net_gain = len(added) - 1 # removed one, added len(added) # Acceptance test (accept if gain > 0, else Metropolis) if net_gain > 0 or gen.random() < np.exp(net_gain / max(T, 1e-12)): # keep the move (already applied) pass else: # revert for y in added: S.remove(y) omitted.add(y) S.add(to_remove) omitted.remove(to_remove) # Return as a sorted list for reproducibility return S for i in range(10): s = heuristic(82, i) print(s, "|s|", len(s))
Standard input is empty
{1, 3, 7, 6, 10, 16, 20, 21, 23, 27, 28, 52, 54, 58, 59, 61, 67, 71, 72, 74, 78, 79} |s| 22
{1, 2, 4, 8, 9, 11, 19, 26, 22, 23, 28, 31, 56, 57, 59, 66, 67, 71, 72, 80, 79, 74} |s| 22
{5, 1, 4, 8, 10, 18, 24, 23, 20, 27, 29, 56, 58, 59, 61, 67, 72, 71, 74, 80, 82, 79} |s| 22
{1, 3, 6, 7, 10, 15, 21, 22, 25, 31, 33, 48, 54, 53, 57, 67, 70, 69, 72, 79, 78, 82} |s| 22
{1, 4, 3, 11, 9, 20, 22, 23, 28, 27, 60, 46, 48, 49, 62, 59, 67, 66, 77, 79, 80, 82} |s| 22
{3, 56, 7, 10, 27, 18, 20, 21, 62, 6, 25, 28, 57, 1, 60, 73, 71, 66, 74, 79, 81, 78} |s| 22
{1, 2, 8, 10, 7, 11, 22, 23, 25, 26, 32, 31, 47, 58, 59, 64, 66, 67, 77, 79, 80, 82} |s| 22
{1, 2, 4, 8, 10, 13, 21, 23, 26, 27, 30, 49, 55, 57, 62, 63, 70, 73, 74, 79, 81, 82} |s| 22
{5, 4, 10, 8, 1, 21, 22, 27, 26, 29, 35, 56, 55, 59, 64, 61, 71, 74, 80, 76, 82} |s| 21
{1, 2, 4, 8, 9, 11, 19, 22, 23, 26, 28, 31, 46, 56, 57, 63, 65, 72, 71, 82, 76, 78} |s| 22