Píxeles separados de forma exclusiva

30

Para una imagen N por N , encuentre un conjunto de píxeles de modo que no haya distancia de separación más de una vez. Es decir, si dos píxeles están separados por una distancia d , entonces son los únicos dos píxeles que están separados exactamente por d (usando la distancia euclidiana ). Tenga en cuenta que d no necesita ser entero.

El desafío es encontrar un conjunto de este tipo más grande que nadie.

Especificación

No se requiere ninguna entrada; para este concurso, N se fijará en 619.

(Dado que la gente sigue preguntando, el número 619 no tiene nada de especial. Se eligió para ser lo suficientemente grande como para hacer improbable una solución óptima, y ​​lo suficientemente pequeño como para permitir que se muestre una imagen N por N sin que Stack Exchange la reduzca automáticamente. Las imágenes pueden ser se muestra en tamaño completo hasta 630 por 630, y decidí ir con el primo más grande que no exceda eso).

La salida es una lista de enteros separados por espacios.

Cada número entero en la salida representa uno de los píxeles, numerados en orden de lectura en inglés desde 0. Por ejemplo, para N = 3, las ubicaciones se numerarían en este orden:

0 1 2
3 4 5
6 7 8

Puede generar información de progreso durante la ejecución si lo desea, siempre que la salida de puntuación final esté fácilmente disponible. Puede enviar a STDOUT o a un archivo o lo que sea más fácil para pegar en el Juez de fragmentos de pila a continuación.

Ejemplo

N = 3

Coordenadas elegidas:

(0,0)
(1,0)
(2,1)

Salida:

0 1 5

Victorioso

La puntuación es el número de ubicaciones en la salida. De las respuestas válidas que tienen el puntaje más alto, gana el primero en publicar la salida con ese puntaje.

Su código no necesita ser determinista. Puede publicar su mejor salida.


Áreas relacionadas para la investigación.

(Gracias a Abulafia por los enlaces de Golomb)

Si bien ninguno de estos es el mismo que este problema, ambos son similares en concepto y pueden darle ideas sobre cómo abordar esto:

Tenga en cuenta que los puntos requeridos para esta pregunta no están sujetos a los mismos requisitos que un rectángulo Golomb. Un rectángulo de Golomb se extiende desde el caso de 1 dimensión al requerir que el vector de cada punto entre sí sea único. Esto significa que puede haber dos puntos separados por una distancia de 2 horizontalmente, y también dos puntos separados por una distancia de 2 verticalmente.

Para esta pregunta, es la distancia escalar la que debe ser única, por lo que no puede haber una separación horizontal y vertical de 2. Cada solución a esta pregunta será un rectángulo de Golomb, pero no todos los rectángulos de Golomb serán una solución válida para esta pregunta.


Límites superiores

Dennis señaló amablemente en el chat que 487 es un límite superior en el puntaje, y dio una prueba:

De acuerdo con mi código CJam ( 619,2m*{2f#:+}%_&,), hay 118800 números únicos que se pueden escribir como la suma de los cuadrados de dos enteros entre 0 y 618 (ambos inclusive). n píxeles requieren n (n-1) / 2 distancias únicas entre sí. Para n = 488, eso da 118828.

Por lo tanto, hay 118.800 longitudes diferentes posibles entre todos los píxeles potenciales en la imagen, y colocar 488 píxeles negros daría como resultado 118.828 longitudes, lo que hace imposible que todos sean únicos.

Me interesaría saber si alguien tiene una prueba de un límite superior más bajo que este.


Tabla de clasificación

(La mejor respuesta por cada usuario)

imagen de la tabla de clasificación


Juez de fragmentos de pila

trichoplax
fuente
Me hubiera encantado ver una respuesta de Piet aquí
C5H8NNaO4
@ C5H8NNaO4 la competición tiene un final abierto - nadie es ni de lejos una solución óptima por lo que hay mucho espacio para nuevas respuestas ...
Trichoplax
Dado que está ofreciendo recompensas tanto para el límite superior comprobado como para la lista experimental de píxeles, supongo que hay algún tipo de aplicación para este problema.
Fatalize
@Fatalize, no que yo sepa, pero me fascinaría saber de uno. El problema similar Costas array tiene aplicaciones prácticas enumeradas pero no he encontrado nada sobre este problema en particular.
trichoplax
1
He estado mirando esto, y creo que n = 487 es el límite superior mínimo en píxeles. Por curiosidad, ¿aceptará una prueba de que no existe un límite superior menor para la recompensa?
Mego

Respuestas:

13

Python 3, 135 136 137

10 6830 20470 47750 370770 148190 306910 373250 267230 354030 30390 361470 118430 58910 197790 348450 381336 21710 183530 305050 2430 1810 365832 99038 381324 39598 262270 365886 341662 15478 9822 365950 44526 58862 24142 381150 31662 237614 118830 380846 7182 113598 306750 11950 373774 111326 272358 64310 43990 200278 381014 165310 254454 12394 382534 87894 6142 750 382478 15982 298326 70142 186478 152126 367166 1162 23426 341074 7306 76210 140770 163410 211106 207962 35282 165266 300178 120106 336110 30958 158 362758 382894 308754 88434 336918 244502 43502 54990 279910 175966 234054 196910 287284 288468 119040 275084 321268 17968 2332 86064 340044 244604 262436 111188 291868 367695 362739 370781 375723 360261 377565 383109 328689 347879 2415 319421 55707 352897 313831 302079 19051 346775 361293 328481 35445 113997 108547 309243 19439 199037 216463 62273 174471 207197 167695 296927

Se encuentra utilizando un algoritmo codicioso que, en cada etapa, elige el píxel válido cuyo conjunto de distancias a los píxeles elegidos se superpone menos con el de otros píxeles.

Específicamente, la puntuación es

score(P) = sum(number of pixels with D in its distance set
               for each D in P's distance set)

y se elige el píxel con la puntuación más baja.

La búsqueda se inicia con el punto 10(es decir (0, 10)). Esta parte es ajustable, por lo que comenzar con diferentes píxeles puede conducir a mejores o peores resultados.

Es un algoritmo bastante lento, así que estoy tratando de agregar optimizaciones / heurísticas, y tal vez algo de retroceso. PyPy se recomienda para la velocidad.

Cualquiera que intente idear un algoritmo debe probarlo N = 10, para lo cual tengo 9 (pero esto requirió muchos ajustes e intentos de diferentes puntos iniciales):

ingrese la descripción de la imagen aquí

Código

from collections import Counter, defaultdict
import sys
import time

N = 619

start_time = time.time()

def norm(p1, p2):
    return (p1//N - p2//N)**2 + (p1%N - p2%N)**2

selected = [10]
selected_dists = {norm(p1, p2) for p1 in selected for p2 in selected if p1 != p2}
pix2dist = {} # {candidate pixel: {distances to chosen}}
dist2pix = defaultdict(set)

for pixel in range(N*N):
    if pixel in selected:
        continue

    dist_list = [norm(pixel, p) for p in selected]
    dist_set = set(dist_list)

    if len(dist_set) != len(dist_list) or dist_set & selected_dists:
        continue

    pix2dist[pixel] = dist_set

    for dist in dist_set:
        dist2pix[dist].add(pixel)

while pix2dist:
    best_score = None
    best_pixel = None

    for pixel in sorted(pix2dist): # Sorting for determinism
        score = sum(len(dist2pix[d]) for d in pix2dist[pixel])

        if best_score is None or score < best_score:
            best_score = score
            best_pixel = pixel

    added_dists = pix2dist[best_pixel]
    selected_dists |= added_dists
    del pix2dist[best_pixel]
    selected.append(best_pixel)

    for d in added_dists:
        dist2pix[d].remove(best_pixel)

    to_remove = set()
    for pixel in pix2dist:
        new_dist = norm(pixel, best_pixel)

        if (new_dist in selected_dists or new_dist in pix2dist[pixel]
                or added_dists & pix2dist[pixel]):
            to_remove.add(pixel)
            continue

        pix2dist[pixel].add(new_dist)
        dist2pix[new_dist].add(pixel)

    for pixel in to_remove:
        for d in pix2dist[pixel]:
            dist2pix[d].remove(pixel)

        del pix2dist[pixel]

    print("Selected: {}, Remaining: {}, Chosen: ({}, {})".format(len(selected), len(pix2dist),
                                                                 best_pixel//N, best_pixel%N))
    sys.stdout.flush()

print(*selected)
print("Time taken:", time.time() - start_time)
Sp3000
fuente
3
Rápidamente forcé la fuerza bruta N=10y hay muchos diseños distintos con 9 pts, pero eso es lo mejor que puedes hacer.
Será
5

SWI-Prolog, puntaje 131

Apenas mejor que la respuesta inicial, pero supongo que esto hará que las cosas comiencen un poco más. El algoritmo es el mismo que la respuesta de Python, excepto por el hecho de que prueba los píxeles de forma alternativa, comenzando con el píxel superior izquierdo (píxel 0), luego el píxel inferior derecho (píxel 383160), luego el píxel 1, luego el píxel 383159 etc.

a(Z) :-
    N = 619,
    build_list(N,Z).

build_list(N,R) :-
    M is N*N,
    get_list([M,-1],[],L),
    reverse(L,O),
    build_list(N,O,[],[],R).

get_list([A,B|C],R,Z) :-
    X is A - 1,
    Y is B + 1,
    (X =< Y,
    Z = R
    ;
    get_list([X,Y,A,B|C],[X,Y|R],Z)).

build_list(_,[],R,_,R) :- !.
build_list(N,[A|T],R,W,Z) :-
    separated_pixel(N,A,R,W,S),
    is_set(S),
    flatten([W|S],V),!,
    build_list(N,T,[A|R],V,Z)
    ;build_list(N,T,R,W,Z).


separated_pixel(N,A,L,W,R) :-
    separated_pixel(N,A,L,[],W,R).

separated_pixel(N,A,[A|T],R,W,S) :-
        separated_pixel(N,A,T,R,W,S).

separated_pixel(N,A,[B|T],R,W,S) :-
    X is (A mod N - B mod N)*(A mod N - B mod N),
    Y is (A//N - B//N)*(A//N - B//N),
    Z is X + Y,
    \+member(Z,W),
    separated_pixel(N,A,T,[Z|R],W,S).

separated_pixel(_,_,[],R,_,R).

Entrada:

a(A).

Salida:

Z = [202089, 180052, 170398, 166825, 235399, 138306, 126354, 261759, 119490, 117393, 281623, 95521, 290446, 299681, 304310, 78491, 314776, 63618, 321423, 60433, 323679, 52092, 331836, 335753, 46989, 40402, 343753, 345805, 36352, 350309, 32701, 32470, 352329, 30256, 28089, 357859, 23290, 360097, 22534, 362132, 20985, 364217, 365098, 17311, 365995, 15965, 15156, 368487, 370980, 371251, 11713, 372078, 372337, 10316, 373699, 8893, 374417, 8313, 7849, 7586, 7289, 6922, 376588, 6121, 5831, 377399, 377639, 4941, 378494, 4490, 379179, 3848, 379453, 3521, 3420, 379963, 380033, 3017, 380409, 2579, 380636, 2450, 2221, 2006, 381235, 1875, 381369, 381442, 381682, 1422, 381784, 1268, 381918, 1087, 382144, 382260, 833, 382399, 697, 382520, 622, 382584, 382647, 382772, 384, 382806, 319, 286, 382915, 382939, 190, 172, 383005, 128, 383050, 93, 383076, 68, 383099, 52, 40, 383131, 21, 383145, 10, 383153, 4, 383158, 1, 383160, 0]

Imagen del fragmento de pila

131 puntos

Fatalizar
fuente
Como hay un máximo teórico de 487, incluso un aumento incremental es significativo ...
trichoplax
¿Su salida como se muestra funciona con el fragmento de pila? Había especificado un espacio separado (como en mi respuesta de ejemplo) pero la razón principal de esto fue para que el Fragmento de pila funcionara.
trichoplax
@trichoplax Sí, es un error tipográfico, empiezo con el píxel 0, lo arreglaré. Para obtener la imagen, seleccioné la parte de la salida entre los dos corchetes y eliminé todas las comas. Sin embargo, el fragmento de pila parece funcionar con píxeles separados por comas.
Fatalize
4

Haskell— 115 130 131 135 136

Mi inspiración fue el Tamiz de Eratóstenes y, en particular, El Tamiz Genuino de Eratóstenes , un artículo de Melissa E. O'Neill del Harvey Mudd College. Mi versión original (que consideraba los puntos en orden de índice) tamizó puntos extremadamente rápido, por alguna razón que no recuerdo, decidí mezclar los puntos antes de "tamizarlos" en esta versión (creo que para facilitar la generación de diferentes respuestas usando una nueva semilla en el generador aleatorio). Debido a que los puntos ya no están en ningún tipo de orden, en realidad ya no hay ningún cribado, y como resultado, lleva un par de minutos producir esta respuesta única de 115 puntos. Un nocaut Vectorprobablemente sería una mejor opción ahora.

Entonces, con esta versión como punto de control, veo dos ramas, volviendo al algoritmo de "Tamiz genuino" y aprovechando la mónada Lista para elegir, o intercambiando las Setoperaciones por equivalentes Vector.

Editar: Entonces, para la versión de trabajo dos, volví hacia el algoritmo de tamiz, mejoré la generación de "múltiplos" (eliminando índices al encontrar puntos en coordenadas enteras en círculos con radio igual a la distancia entre dos puntos, similar a generar múltiplos primos) ) y haciendo algunas mejoras constantes en el tiempo evitando algunos recálculos innecesarios.

Por alguna razón, no puedo volver a compilar con la creación de perfiles activada, pero creo que el principal cuello de botella ahora es retroceder. Creo que explorar un poco de paralelismo y concurrencia producirá aceleraciones lineales, pero el agotamiento de la memoria probablemente me limitará a una mejora de 2x.

Editar: La versión 3 divagó un poco, primero experimenté con una heurística al tomar los siguientes índices 𝐧 (después de tamizar las opciones anteriores) y elegir el que produjo el siguiente conjunto mínimo de nocaut. Esto terminó siendo demasiado lento, así que volví a un método de fuerza bruta en todo el espacio de búsqueda. Se me ocurrió una idea de ordenar los puntos por distancia desde algún origen, y me llevó a una mejora en un solo punto (en el tiempo que duró mi paciencia). Esta versión elige el índice 0 como el origen, puede valer la pena probar el punto central del avión.

Editar: recogí 4 puntos al reordenar el espacio de búsqueda para priorizar los puntos más distantes del centro. Si está probando mi código, 135 136 es en realidad la segunda tercera solución encontrada. Edición rápida: esta versión parece la más propensa a seguir siendo productiva si se deja en ejecución. Sospecho que puedo empatar a 137, y luego quedarse sin paciencia esperando 138.

Una cosa que noté (que puede ser de ayuda para alguien) es que si establece el orden de puntos desde el centro del plano (es decir, quita (d*d -)de originDistance) la imagen formada se parece un poco a una espiral primaria escasa.

{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE BangPatterns #-}

module Main where

import Data.Function (on)
import Data.List     (tails, sortBy)
import Data.Maybe    (fromJust)
import Data.Ratio
import Data.Set      (fromList, toList, union, difference, member)

import System.IO

sideLength :: Int
sideLength = 619

data Point = Point {  x :: !Int,  y :: !Int } deriving (Ord, Eq)
data Delta = Delta { da :: !Int, db :: !Int }

euclidean :: Delta -> Int
euclidean Delta{..} = da*da + db*db

instance Eq Delta where
  (==) = (==) `on` euclidean

instance Ord Delta where
  compare = compare `on` euclidean

delta :: Point -> Point -> Delta
delta a b = Delta (min dx dy) (max dx dy)
  where
    dx = abs (x a - x b)
    dy = abs (y a - y b)

equidistant :: Dimension -> Point -> Point -> [Point]
equidistant d a b =
  let
    (dx, dy) = (x a - x b, y a - y b)
    m = if dx == 0 then Nothing else Just (dy % dx)                    -- Slope
    w = if dy == 0 then Nothing else Just $ maybe 0 (negate . recip) m -- Negative reciprocal
    justW = fromJust w -- Moral bankruptcy
    (px, py) = ((x a + x b) % 2, (y a + y b) % 2)                      -- Midpoint
    b0 = py - (justW * px)                                             -- Y-intercept
    f q = justW * q + b0                                               -- Perpendicular bisector
  in
   maybe (if denominator px == 1 then map (Point (numerator px)) [0..d - 1] else [])
         ( map (\q -> Point q (numerator . f . fromIntegral $ q))
         . filter ((== 1) . denominator . f . fromIntegral)
         )
         (w >> return [0..d - 1])

circle :: Dimension -> Point -> Delta -> [Point]
circle d p delta' =
  let
    square = (^(2 :: Int))
    hypoteneuse = euclidean delta'
    candidates = takeWhile ((<= hypoteneuse) . square) [0..d - 1]
    candidatesSet = fromList $ map square [0..d - 1]
    legs = filter ((`member` candidatesSet) . (hypoteneuse -) . square) candidates
    pythagoreans = zipWith Delta legs
                 $ map (\l -> floor . sqrt . (fromIntegral :: Int -> Double) $ hypoteneuse - square l) legs
  in
    toList . fromList $ concatMap (knight p) pythagoreans

knight :: Point -> Delta -> [Point]
knight Point{..} Delta{..} =
    [ Point (x + da) (y - db), Point (x + da) (y + db)
    , Point (x + db) (y - da), Point (x + db) (y + da)
    , Point (x - da) (y - db), Point (x - da) (y + db)
    , Point (x - db) (y - da), Point (x - db) (y + da)
    ]

type Dimension = Int
type Index = Int

index :: Dimension -> Point -> Index
index d Point{..} = y * d + x

point :: Dimension -> Index -> Point
point d i = Point (i `rem` d) (i `div` d)

valid :: Dimension -> Point -> Bool
valid d Point{..} = 0 <= x && x < d
                 && 0 <= y && y < d

isLT :: Ordering -> Bool
isLT LT = True
isLT _  = False

sieve :: Dimension -> [[Point]]
sieve d = [i0 : sieve' is0 [i0] [] | (i0:is0) <- tails . sortBy originDistance . map (point d) $ [0..d*d - 1]]
  where
    originDistance :: Point -> Point -> Ordering
    originDistance = compare `on` ((d*d -) . euclidean . delta (point d (d*d `div` 2)))

    sieve' :: [Point] -> [Point] -> [Delta] -> [Point]
    sieve' []     _  _ = []
    sieve' (i:is) ps ds = i : sieve' is' (i:ps) ds'
      where
        ds' = map (delta i) ps ++ ds
        knockouts = fromList [k | d' <- ds
                                , k  <- circle d i d'
                                , valid d k
                                , not . isLT $ k `originDistance` i
                                ]
            `union` fromList [k | q  <- i : ps
                                , d' <- map (delta i) ps
                                , k  <- circle d q d'
                                , valid d k
                                , not . isLT $ k `originDistance` i
                                ]
            `union` fromList [e | q <- ps
                                , e <- equidistant d i q
                                , valid d e
                                , not . isLT $ e `originDistance` i
                                ]
        is' = sortBy originDistance . toList $ fromList is `difference` knockouts

main :: IO ()
main = do let answers = strictlyIncreasingLength . map (map (index sideLength)) $ sieve sideLength
          hSetBuffering stdout LineBuffering
          mapM_ (putStrLn . unwords . map show) $ answers
  where
    strictlyIncreasingLength :: [[a]] -> [[a]]
    strictlyIncreasingLength = go 0
      where
        go _ []     = []
        go n (x:xs) = if n < length x then x : go (length x) xs else go n xs

Salida

1237 381923 382543 382541 1238 1857 380066 5 380687 378828 611 5571 382553 377587 375113 3705 8664 376356 602 1253 381942 370161 12376 15475 7413 383131 367691 380092 376373 362114 36 4921 368291 19180 382503 26617 3052 359029 353451 29716 382596 372674 352203 8091 25395 12959 382479 381987 35894 346031 1166 371346 336118 48276 2555 332400 46433 29675 380597 13066 382019 1138 339859 368230 29142 58174 315070 326847 56345 337940 2590 382663 320627 70553 19278 7309 82942 84804 64399 5707 461 286598 363864 292161 89126 371267 377122 270502 109556 263694 43864 382957 824 303886 248218 18417 347372 282290 144227 354820 382909 380301 382808 334361 375341 2197 260623 222212 196214 231526 177637 29884 251280 366739 39442 143568 132420 334718 160894 353132 78125 306866 140600 297272 54150 240054 98840 219257 189278 94968 226987 265881 180959 142006 218763 214475
RB
fuente
Impresionantes mejoras. Te quedan 2 horas para llegar a 138 antes de que se asigne la recompensa. Buen trabajo de cualquier manera ...
trichoplax
Parece poco probable que cumpla con ese objetivo, todavía no he logrado generar un conjunto de 137 elementos. Creo que este método probablemente está aprovechado ...
RB
Es interesante que dos respuestas diferentes con enfoques diferentes estén alcanzando un máximo alrededor del mismo tamaño.
trichoplax
Creo que el límite superior probablemente esté bastante cerca. Considere un plano infinito y dos puntos cualesquiera. La ubicación óptima de esos puntos con cualquier distancia dminimiza el número de otros puntos excluidos de la consideración al trazar círculos de radio dcon centros de ambos puntos elegidos, donde el perímetro solo toca otras tres coordenadas enteras (a 90, 180 y 270 grados gira alrededor el círculo) y la línea de bisección perpendicular que no cruza coordenadas enteras. Por lo tanto, cada nuevo punto n+1excluirá 6notros puntos de consideración (con una elección óptima).
RB
3

Python 3, puntaje 129

Este es un ejemplo de respuesta para comenzar las cosas.

Solo un enfoque ingenuo recorriendo los píxeles en orden y eligiendo el primer píxel que no causa una distancia de separación duplicada, hasta que los píxeles se agoten.

Código

width = 619
height = 619
area = width * height
currentAttempt = 0

temporaryLengths = []
lengths = []
points = []
pixels = []
for i in range(area):
    pixels.append(0)


def generate_points():
    global lengths
    while True:
        candidate = vacantPixel()
        if isUnique(candidate):
            lengths += temporaryLengths
            pixels[candidate] = 1
            points.append(candidate)
            print(candidate)
        if currentAttempt == area:
            break
    filename = 'uniquely-separated-points.txt'
    with open(filename, 'w') as file:
        file.write(' '.join(points))


def isUnique(n):
    x = n % width
    y = int(n / width)
    temporaryLengths[:] = []
    for i in range(len(points)):
        point = points[i]
        a = point % width
        b = int(point / width)
        d = distance(x, y, a, b)
        if d in lengths or d in temporaryLengths: 
            return False
        temporaryLengths.append(d)
    return True


def distance(x1, y1, x2, y2):
    xd = x2 - x1
    yd = y2 - y1
    return (xd*xd + yd*yd) ** 0.5


def vacantPixel():
    global currentAttempt
    while True:
        n = currentAttempt
        currentAttempt += 1
        if pixels[n] == 0:
            break
    return n


generate_points()

Salida

0 1 3 7 12 20 30 44 65 80 96 122 147 181 203 251 289 360 400 474 564 592 627 660 747 890 1002 1155 1289 1417 1701 1789 1895 2101 2162 2560 2609 3085 3121 3331 3607 4009 4084 4242 4495 5374 5695 6424 6762 6808 7250 8026 8356 9001 9694 10098 11625 12881 13730 14778 15321 16091 16498 18507 19744 20163 20895 23179 25336 27397 31366 32512 33415 33949 39242 41075 46730 47394 48377 59911 61256 66285 69786 73684 79197 89530 95447 102317 107717 111751 116167 123198 126807 130541 149163 149885 154285 159655 163397 173667 173872 176305 189079 195987 206740 209329 214653 220911 230561 240814 249310 269071 274262 276855 285295 305962 306385 306515 312310 314505 324368 328071 348061 350671 351971 354092 361387 369933 376153

Imagen del fragmento de pila

imagen de 129 píxeles separados de forma exclusiva

trichoplax
fuente
3

Pitón 3, 130

A modo de comparación, aquí hay una implementación recursiva de backtracker:

N = 619

def norm(p1, p2):
    return (p1//N - p2//N)**2 + (p1%N - p2%N)**2

def solve(selected, dists):
    global best

    if len(selected) > best:
        print(len(selected), "|", *selected)
        best = len(selected)

    for pixel in (range(selected[-1]+1, N*N) if selected else range((N+1)//2+1)):
        # By symmetry, place first pixel in first half of top row
        added_dists = [norm(pixel, p) for p in selected]
        added_set = set(added_dists)

        if len(added_set) != len(added_dists) or added_set & dists:
            continue

        selected.append(pixel)
        dists |= added_set

        solve(selected, dists)

        selected.pop()
        dists -= added_set

print("N =", N)
best = 0
selected = []
dists = set()
solve(selected, dists)

Encuentra la siguiente solución de 130 píxeles rápidamente antes de que comience a ahogarse:

0 1 3 7 12 20 30 44 65 80 96 122 147 181 203 251 289 360 400 474 564 592 627 660 747 890 1002 1155 1289 1417 1701 1789 1895 2101 2162 2560 2609 3085 3121 3331 3607 4009 4084 4242 4495 5374 5695 6424 6762 6808 7250 8026 8356 9001 9694 10098 11625 12881 13730 14778 15321 16091 16498 18507 19744 20163 20895 23179 25336 27397 31366 32512 33415 33949 39242 41075 46730 47394 48377 59911 61256 66285 69786 73684 79197 89530 95447 102317 107717 111751 116167 123198 126807 130541 149163 149885 154285 159655 163397 173667 173872 176305 189079 195987 206740 209329 214653 220911 230561 240814 249310 269071 274262 276855 285295 305962 306385 306515 312310 314505 324368 328071 348061 350671 351971 354092 361387 371800 376153 378169

Más importante aún, lo estoy usando para verificar soluciones para casos pequeños. Para N <= 8, los óptimos son:

1: 1 (0)
2: 2 (0 1)
3: 3 (0 1 5)
4: 4 (0 1 6 12)
5: 5 (0 1 4 11 23)
6: 6 (0 1 9 23 32 35)
7: 7 (0 2 9 20 21 40 48)
8: 7 (0 1 3 12 22 56 61)
9: 8 (0 1 3 8 15 37 62 77)
10: 9 (0 1 7 12 30 53 69 80 89)

Entre paréntesis se encuentran los primeros óptimos lexicográficos.

Inconfirmado:

11: 10 (0 2 3 7 21 59 66 95 107 120)
12: 10 (0 1 3 7 33 44 78 121 130 140)
Sp3000
fuente
3

Scala, 132

Escanea de izquierda a derecha y de arriba a abajo como la solución ingenua, pero intenta comenzar en diferentes ubicaciones de píxeles.

import math.pow
import math.sqrt

val height, width = 619
val area = height * width

case class Point(x: Int, y: Int)

def generate(n: Int): Set[Point] = {

  def distance(p: Point, q: Point) = {
    def square(x: Int) = x * x
    sqrt(square(q.x - p.x) + square(q.y - p.y))
  }

  def hasDuplicates(s: Seq[_]) = s.toSet.size != s.size

  def rotate(s: Vector[Point]): Vector[Point] = s.drop(n) ++ s.take(n)

  val remaining: Vector[Point] =
    rotate((for (y <- 0 until height; x <- 0 until width) yield { Point(x, y) }).toVector)
  var unique = Set.empty[Point]
  var distances = Set.empty[Double]
  for (candidate <- remaining) {
    if (!unique.exists(p => distances.contains(distance(candidate, p)))) {
      val candidateDistances = unique.toSeq.map(p => distance(candidate, p))
      if (!hasDuplicates(candidateDistances)) {
        unique = unique + candidate
        distances = distances ++ candidateDistances
      }
    }
  }
  unique
}

def print(s: Set[Point]) = {
  def toRowMajor(p: Point) = p.y*height + p.x
  println(bestPixels.map(toRowMajor).toSeq.sorted.mkString(" "))
}

var bestPixels = Set.empty[Point]
for (n <- 0 until area) {                                                                                                                                                                                          
  val pixels = generate(n)
  if (pixels.size > bestPixels.size) bestPixels = pixels
}
print(bestPixels)

Salida

302 303 305 309 314 322 332 346 367 382 398 424 449 483 505 553 591 619 647 680 719 813 862 945 1014 1247 1459 1700 1740 1811 1861 1979 2301 2511 2681 2913 3114 3262 3368 4253 4483 4608 4753 5202 5522 5760 6246 6474 6579 6795 7498 8062 8573 8664 9903 10023 10567 10790 11136 12000 14153 15908 17314 17507 19331 20563 20941 22339 25131 26454 28475 31656 38328 39226 40214 50838 53240 56316 60690 61745 62374 68522 71208 78598 80204 86005 89218 93388 101623 112924 115702 118324 123874 132852 136186 139775 144948 154274 159730 182200 193642 203150 203616 213145 214149 218519 219744 226729 240795 243327 261196 262036 271094 278680 282306 289651 303297 311298 315371 318124 321962 330614 336472 343091 346698 354881 359476 361983 366972 369552 380486 382491
Dave Swartz
fuente
3
Simplemente haciendo rodar la pelota ...
Dave Swartz
3

Pitón, 134 132

Aquí hay uno simple que selecciona al azar parte del espacio de búsqueda para cubrir un área mayor. Repite los puntos en la distancia de un orden de origen. Se salta puntos que están a la misma distancia del origen, y salta temprano si no puede mejorar de la mejor manera. Funciona indefinidamente.

from random import *
from bisect import *

W = H = 619
pts = []
deepest = 0
lengths = set()

def place(x, y):
    global lengths
    pos = (x, y)
    for px, py in pts:
        dist = (x-px)*(x-px) + (y-py)*(y-py)
        if dist in lengths:
            return False
    dists = set((x-px)*(x-px) + (y-py)*(y-py) for px, py in pts)
    if len(dists) != len(pts):
        return False
    lengths |= dists
    pts.append(pos)
    return True

def unplace():
    x, y = pos = pts.pop()
    for px, py in pts:
        dist = (x-px)*(x-px) + (y-py)*(y-py)
        lengths.remove(dist)

def walk(i):
    global deepest, backtrack
    depth = len(pts)
    while i < W*H:
        d, x, y, rem = order[i]
        if rem+depth <= deepest: # early out if remaining unique distances mean we can't improve
            return
        i += 1
        if place(x, y):
            j = i
            while j < W*H and order[j][0] == d: # skip those the same distance from origin
                j += 1
            walk(j)
            unplace()
            if backtrack <= depth:
                break
            if not randint(0, 5): # time to give up and explore elsewhere?
                backtrack = randint(0, len(pts))
                break
            backtrack = W*H # remove restriction
    if depth >= deepest:
        deepest = depth
        print (ox, oy), depth, "=", " ".join(str(y*W+x) for x, y in pts)

try:
    primes = (0,1,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97)
    while True:
        backtrack = W*H
        ox, oy = choice(primes), choice(primes) # random origin coordinates
        order = sorted((float((ox-x)**2+(oy-y)**2)+random(), x, y) for x in xrange(W) for y in xrange(H))
        rem = sorted(set(int(o[0]) for o in order)) # ordered list of unique distances
        rem = {r: len(rem)-bisect_right(rem, r) for r in rem} # for each unique distance, how many remain?
        order = tuple((int(d), x, y, rem[int(d)]) for i, (d, x, y) in enumerate(order))
        walk(0)
except KeyboardInterrupt:
    print

Rápidamente encuentra soluciones con 134 puntos:

30 113.313 88.637 122.569 11.956 36.098 79.401 61.471 135.610 31.796 4.570 150.418 57.797 4.581 125.201 151.128 115.936 165.898 127.697 162.290 33.091 20.098 189.414 187.620 186.440 91.290 206.766 35.619 69.033 351 186 511 129 058 228 458 69 065 226 046 210 035 235 925 164 324 18 967 254 416 130 970 17 753 248 978 57 376 276 798 456 283 541 293 423 257 747 204 626 298 427 249115 21544 95185 231226 54354 104483 280665 518 147181 318363 1793 248609 82260 52568 365227 361603 346849 331462 69310 90988 341446 229599 277828 382837 339014 323612 365040 269883 307597 374347 333221202204887 307597 374347 316282 3546

Para los curiosos, aquí hay algunos pequeños N forzados por fuerza bruta:

3  =  0  2  3
4  =  0  2  4  7
5  =  0  2  5 17 23
6  =  0 12 21 28 29 30
7  =  4  6 11 14 27 36 42
8  =  0  2  8 11 42 55 56
9  =  0  2  9 12 26 50 63 71
10 =  0  2  7 10 35 75 86 89  93
11 =  0 23 31 65 66 75 77 95 114 117
Será
fuente
¿Has intentado ejecutar esto a través de PyPy ?
trichoplax
1
@trichoplax Siempre ejecuto estas cosas de hobby tanto en pypy como en cpython, y si cpython es más rápido, alojo boletos en pypy. En este caso en particular, pypy es bastante más rápido que cpython y así es como obtuve estos números :)
Será el
Estoy interesado, ¿qué implica "rápidamente"?
Caín
@Cain 'rápidamente' fue alrededor de 5 minutos iirc
Will
2

Fantom 96

Utilicé un algoritmo de evolución, básicamente agregué k puntos aleatorios a la vez, lo hice para j diferentes conjuntos aleatorios, luego elegí el mejor y repítelo. Respuesta bastante terrible en este momento, pero eso lo está ejecutando con solo 2 niños por generación en aras de la velocidad, que es casi al azar. Voy a jugar un poco con los parámetros para ver cómo funciona, y probablemente necesito una mejor función de puntuación que el número de puntos libres restantes.

class Pixel
{
  static const Int n := 619
  static const Int stepSize := 20
  static const Int generationSize := 5
  static const |Int, Int -> Int| d := |Int x, Int y -> Int| {
      d1 := x%n - y%n
      d2 := x/n - y/n
      return d1.pow(2) + d2.pow(2)
    }


  public static Void main(){

    //Initialize

    [Int: Int[][]] disMap := [:]
    Int[] freeSpots := (0..<n*n).toList
    Int[] pixels := [,]
    Int[] distances := [,]





    genNum := 0
    children := [,]
    while(freeSpots.size > 0){
      echo("Generation: ${genNum++} \t Spots Left: ${freeSpots.size} \t Pixels added: $pixels.size \t Distances used: $distances.size uniqueDistances: $distances.unique.size" )
      echo(distances)
      echo("Pixels: " + pixels.join(" "))
      //echo("Distances: $distances")
      //Generate children
      children = [,]
      generationSize.times{
        //echo("\tStarting child $it")
        i := Int.random(0..<freeSpots.size)
        childFreeSpots := freeSpots.dup
        childPixels := pixels.dup
        childDistances := distances.dup

        for(Int step := 0; step < stepSize; step++){

          if( i < childFreeSpots.size){
            //Choose a pixel
            pixel := childFreeSpots.removeAt(i)
            //echo("\t\tAdding pixel $pixel")

            //Remove neighbors that are the new distances away
            ///Find distances
            newDis := [,]
            childPixels.each { 
              newDis.add(d(pixel, it))
            }

            //Check that there are no equal distances
            if(newDis.size != newDis.unique.size) continue



            //Remove neighbors
            childPixels.each | Int childPixel|{
              newDis.each |Int dis|{
                neighbors := getNeighbors(childPixel, dis, disMap)
                neighbors.each| Int n |{
                  index := childFreeSpots.binarySearch(n)
                  if(index >= 0) childFreeSpots.removeAt(index)
                }
              }
            }
            //echo("Removed neighbors: $test")
            //Remove all the neighbors of new pixel
            childDistances.addAll(newDis)
            childDistances.each|Int dis| {   
              neighbors := getNeighbors(pixel, dis, disMap)
              childFreeSpots.removeAll(neighbors)
            }

            //Add new pixel
            childPixels.add(pixel)  
          }
        }
        children.add([childPixels.dup, childDistances.dup, childFreeSpots.dup])
        echo("\tChild $it: pixels: $childPixels.size \t distances: $childDistances.size \t freeSpots: $childFreeSpots.size")
      }

      //Score children and keep best one as new parent
      Obj?[][] parent := children.max |Int[][] a, Int[][] b -> Int| { return (a.last.size  + a.first.size*10000) <=> (b.last.size + b.first.size*10000)  }
      pixels = parent.first
      distances = parent[1]
      freeSpots = parent.last

    }//End while


    //Return result
    echo("Size: " + pixels.size)
    echo(pixels.join(" "))





  }

  private static Bool checkValid(Int[] pixels){
    distances := [,]
    pixels[0..-2].each|Int p, Int i|{
      for(Int j := i + 1; j < pixels.size; j++){
        distances.add(d(p, pixels[j]))
      }
    }
    if(distances.size > distances.unique.size){
      echo("Duplicate distance found!!!!")
      echo("Pixel $pixels.last is not valid")
      return false
    }
    return true
  }

  public static Int[] getNeighbors(Int spot, Int distance, [Int : Int[][]] disMap ){
    result := [,]
    //Check hash map
    pairs := disMap.get(distance, null)

    //Find possible int pairs if not already in the map
    if(pairs == null){
      for(Int i := 0; i*i <= distance; i++ ){
        for(Int j := i; j*j + i*i <= distance; j++){
          if(i.pow(2) + j.pow(2) == distance){
            pairs.add([i, j])
          }
        }
      }
      disMap.add(distance, pairs)
    }

    pairs.each|Int[] pair|{
      //Find neighbors with pair
      x := pair.first
      y := pair.last
      2.times{ 
        //Positive x
        result.add(spot + x + y*n)
        result.add(spot + x - y*n)

        //negative x
        result.add(spot - x + y*n)
        result.add(spot - x - y*n)

        //Swap x and y and repeat
        temp := x
        x = y
        y = temp
      }
    }

    return result.findAll |Int i -> Bool| { i >= 0 }.unique
  }

}

Salida

17595 17596 17601 17627 17670 17726 17778 17861 17956 18117 18324 18733 19145 19597 20244 21139 21857 22742 24078 25343 28577 30152 32027 34406 37008 39864 42313 44820 48049 52193 55496 59707 64551 69976 74152 79758 84392 91782 98996 104625 150212 158877 169579 178660 189201 201343 213643 225998 238177 251012 263553 276797 290790 304915 319247 332702 347266 359665 373683 125899 144678 170677 195503 220092 244336 269861 289473 308633 326736 343756 358781 374280 131880 172485 212011 245015 277131 302055 321747 347911 363717 379166 249798 284200 313870 331913 360712 378024 9704 141872 249686 293656 357038 357596 370392 381963
Caín
fuente
1
Oh wow, tienes razón, lo siento. Hmm, no debe haber copiado todo temprano cuando lo probé. Arreglaré lo que sea que esté sucediendo y responderé con una actualización
Cain el
Ahh, lo descubrí, cuando agregué un nuevo píxel, no estaba comprobando que no fuera equidistante de otros dos píxeles
Caín el
Lo arreglé, pero realmente apesta ahora, creo que podría estar encontrando accidentalmente una peor solución en lugar de la mejor
Cain
Al menos funciona ahora, por lo que puede ajustar los parámetros y ver si puede mejorar el resultado. Genial para ver otro nuevo enfoque. +1
trichoplax
1

Pitón 3, 119

Ya no recuerdo por qué llamé a esta función mc_usp, aunque sospecho que tenía algo que ver con las cadenas de Markov. Aquí publico mi código que ejecuté con PyPy durante aproximadamente 7 horas. El programa intenta construir 100 conjuntos diferentes de píxeles seleccionando píxeles al azar hasta que haya verificado cada píxel de la imagen y devuelva uno de los mejores conjuntos.

En otra nota, en algún momento, realmente deberíamos intentar encontrar un límite superior para N=619que sea mejor que 488, porque a juzgar por las respuestas aquí, ese número es demasiado alto. El comentario de Rowan Blush sobre cómo cada punto nuevo n+1puede eliminar 6*npuntos con una elección óptima parecía una buena idea. Desafortunadamente, tras la inspección de la fórmula a(1) = 1; a(n+1) = a(n) + 6*n + 1, ¿dónde a(n)está el número de puntos eliminados después de agregar npuntos a nuestro conjunto, esta idea puede no ser la mejor opción? Verificando cuándo a(n)es mayor que N**2, a(200)ser más grande de lo que 619**2parece prometedor, pero cuanto a(n)más grande 10**2es a(7)y hemos demostrado que 9 es el límite superior real paraN=10. Los mantendré informados mientras trato de buscar un mejor límite superior, pero cualquier sugerencia es bienvenida.

En mi respuesta. Primero, mi conjunto de 119 píxeles.

15092 27213 294010 340676 353925 187345 127347 21039 28187 4607 23476 324112 375223 174798 246025 185935 186668 138651 273347 318338 175447 316166 158342 97442 361309 251283 29986 98029 339602 292202 304041 353401 236737 324696 42096 102574 357602 66845 40159 57866 3291 24583 254208 357748 304592 86863 19270 228963 87315 355845 55101 282039 83682 55643 292167 268632 118162 48494 378303 128634 117583 841 178939 20941 161231 247142 110205 211040 90946 170124 362592 327093 336321 291050 29880 279825 212675 138043 344012 187576 168354 28193 331713 329875 321927 129452 163450 1949 186448 50734 14422 3761 322400 318075 77824 36391 31016 33491 360713 352240 45316 79905 376004 310778 382640 383077 359178 14245 275451 362125 268047 23437 239772 299047 294065 46335 112345 382617 79986

En segundo lugar, mi código, que selecciona aleatoriamente un punto de partida de un octante del cuadrado 619x619 (ya que el punto de partida es igual bajo rotación y reflexión) y luego cada dos puntos del resto del cuadrado.

import random
import time

start_time = time.time()
print(start_time)

def mc_usp_v3(N, z, k=100, m=1.0):
    """
    At m=1.0, it keeps randomly picking points until we've checked every point. Oh dear.
    """
    ceil = -(-N//2)
    a=random.randint(0,ceil)
    b=random.randint(a,ceil)
    r=[a*N+b]

    best_overall = r[:]
    all_best = []
    best_in_shuffle = r[:]
    num_shuffles = 0
    num_missteps = 0
    len_best = 1

    while num_shuffles < k and len(best_overall) < z:
        dist = []
        missteps = []
        points_left = list(range(N*N))
        points_left.remove(r[0])

        while len_best + num_missteps < m*N*N and len(points_left):
            index = random.randint(0, len(points_left)-1)
            point = points_left[index]
            points_left.pop(index)
            dist, better = euclid(r, point, dist, N)

            if better and len(r) + 1 > len_best:
                r.append(point)
                best_in_shuffle = r[:]
                len_best += 1
            else:
                missteps.append(point)
                num_missteps += 1

        else:
            print(num_shuffles, len(best_overall), len_best, num_missteps, time.time() - start_time)

            num_shuffles += 1
            num_missteps = 0
            missteps = []

            if len(best_in_shuffle) == len(best_overall):
                all_best.append(best_in_shuffle)
                print(best_in_shuffle)

            if len(best_in_shuffle) > len(best_overall):
                best_overall = best_in_shuffle[:]
                all_best = [best_overall]
                print(best_overall)
            a=random.randint(0,ceil)
            b=random.randint(a,ceil)
            r=[a*N+b]
            best_in_shuffle = r[:]
            len_best = 1
    return len(best_overall), all_best

def euclid(point_set, new_point, dist, N):
    new_dist = []
    unique = True
    a,b=divmod(new_point, N)
    for point in point_set:
        c,d=divmod(point, N)
        current_dist = (a-c)**2+(b-d)**2
        if current_dist in dist or current_dist in new_dist:
            unique = False
            break
        new_dist.append(current_dist)
    if unique:
        dist += new_dist
    return dist, unique

def mcusp_format(mcusp_results):
    length, all_best = mcusp_results
    return " ".join(str(i) for i in all_best[0])

print(mcusp_format(mc_usp_v3(10, 20, 100, 1.0)))
print(mcusp_format(mc_usp_v3(619, 488, 100, 1.0)))
print(time.time()-start_time)
Sherlock9
fuente