Rimozione di punti da una matrice triangolare senza perdere triangoli


17

Ho un problema combinatorio che vorrei mettere sull'OEIS , il problema è che non ho abbastanza termini. Questa sfida del codice è di aiutarmi a calcolare più termini e il vincitore sarà l'utente con l'invio contenente il maggior numero di termini.


Il problema

Supponiamo che io ti dia una serie triangolare di lampadine con lunghezza laterale :n

     o
    o o
   o o o
  o o o o
 o o o o o
o o o o o o
1 2  ...  n

Accenderò tre lampadine che formano un triangolo equilatero "verticale" come nell'esempio seguente:

     o
    o x
   o o o
  o o o o
 o x o o x
o o o o o o

Prima di accendere le luci, il tuo compito è rimuovere quante più lampadine possibili dall'array, senza perdere la capacità di dedurre il triangolo delle lampadine che è stato acceso. Per essere chiari, se una lampadina è stata rimossa, non si accende quando la sua posizione è accesa.

Ad esempio, se hai rimosso le seguenti lampadine (contrassegnate da .) vedresti solo le seguenti due luci accese (contrassegnate da x), che è abbastanza dedurre in modo univoco la terza posizione (non illuminata):

     .              .
    . o            . x
   . . o          . . o
  o o o .   =>   o o o .
 o o o o .      o x o o . <- the third unlit position
o . . . o o    o . . . o o

Lascia che a(n)sia il numero massimo di lampadine che è possibile rimuovere senza introdurre ambiguità.


Esempio

Con un algoritmo ingenuo, ho verificato i valori fino a un triangolo con lunghezza laterale 7, come mostrato di seguito:

                                                                      .
                                                      .              . o
                                        .            . o            o . o
                           .           . .          . . o          . o o .
              .           . .         . o o        o o o .        o o . o .
 .           . .         . o o       o o . o      o o o o .      o . o . o o
. .         . o o       . o o o     o . . o o    o . . . o o    o . o . o o o

a(2) = 3    a(3) = 4    a(4) = 5    a(5) = 7     a(6) = 9       a(7) = 11

punteggio

[a(2), a(3), ..., a(n)]Vince l'invio che calcola la sequenza per la n maggiore. Se due invii hanno sequenze identiche, vince quello pubblicato in precedenza.

Sebbene non sia necessario per l'invio, sarebbe istruttivo per me pubblicare una costruzione degli array triangluar risultanti, come nell'esempio sopra.


1
Non è una sfida al codice piuttosto che un codice più veloce?
Don mille,

6
Penso che dovresti scegliere un limite di tempo (diciamo, anni '60) in modo che il concorso non riguardi quanto tempo qualcuno ha trascorso a gestire il proprio codice.
dylnan,

Bel problema. Cosa intendi con triangolo "verticale"?
Damien,

Risposte:


10

Python 3 ,n=8

import itertools
from ortools.sat.python import cp_model


def solve(n):
    model = cp_model.CpModel()
    solver = cp_model.CpSolver()
    cells = {
        (y, x): model.NewBoolVar(str((y, x)))
        for y in range(n) for x in range(y + 1)}
    triangles = [
            {cells[v] for v in ((y1, x1), (y2, x1), (y2, x1 + y2 - y1))}
            for (y1, x1) in cells.keys() for y2 in range(y1 + 1, n)]
    for t1, t2 in itertools.combinations(triangles, 2):
        model.AddBoolOr(t1.symmetric_difference(t2))
    model.Minimize(sum(cells.values()))
    solver.Solve(model)
    return len(cells) - round(solver.ObjectiveValue())


for n in itertools.count(2):
    print('a(%d) = %d' % (n, solve(n)))

Utilizza il solutore CP-SAT di Google OR-Tools .

Dopo aver eseguito per circa 30 secondi, genera quanto segue:

a(2) = 3
a(3) = 4
a(4) = 5
a(5) = 7
a(6) = 9
a(7) = 11
a(8) = 13

Non ho provato ad aspettare perché n=9probabilmente impiegherebbero ore (il numero di vincoli cresce come )n6 . Dopo meno di 30 minuti di calcolo ho scoperto che a(9)=15. Sto lasciando il mio punteggio come n=8perché al momento i vincoli temporali non sono chiari, ma mezz'ora è probabilmente troppo lunga.

Come funziona

Prendi due triangoli equilateri distinti e . Per evitare ambiguità, dovrebbe esserci almeno una lampadina su un vertice che appartiene ad esattamente uno dei e .T 2 T 1 T 2T1T2T1T2

Quindi la domanda può essere riformulata come un problema SAT, con un vincolo per ogni coppia di triangoli.

PS: Mi piacerebbe molto includere un esempio per n=8, ma sto riscontrando problemi con il solutore SAT che apparentemente vuole mantenere le soluzioni tutte per sé.


Decido di trasferire la soluzione a Mathematica , che purtroppo è più lenta.
user202729,

2

Ottenere le soluzioni dal programma di @ Delfad0r

Ho esteso il programma di @ Delfad0r alle soluzioni di output. Fornisce anche risultati intermedi, in modo da ottenere un output in questo modo:

Solving n = 8:
a(8) >= 9
a(8) >= 10
a(8) >= 11
a(8) >= 12
a(8) >= 13
       o
      . o
     . o o
    . o o .
   o o . o o
  o o o o . .
 o . . o o o .
o . . o . o o o
a(8) = 13

Solving n = 9:
a(9) >= 10
a(9) >= 13
a(9) >= 14
a(9) >= 15
        o
       o o
      o . .
     o . o o
    . o . o o
   . o o o o o
  o o o . o . .
 o o o . . . o o
. o o o o o o . .
a(9) = 15

Questo calcolo ha richiesto diverse ore.

Se si diventa impazienti e si preme Ctrl-Cdopo aver trovato una soluzione forse non ottimale, il programma mostrerà quella soluzione. Quindi non ci vuole molto per ottenere questo:

                   .
                  o o
                 . o o
                . o o o
               o o . o o
              o . o o o .
             o . o . o o o
            . o o o o o . o
           o . . o o o o o o
          o o o o o o o o o .
         o o . o o o o . o o o
        o o o o o o . o . o o o
       o . o o . o o o o o o o o
      o o o . o o o o o . o o o o
     o o o . o o o o o o o o . . o
    o o o o o o o o o o o . o . . o
   o o o o . o o o o . o o o o o . o
  o o o o o o o o . o o . . o o o o .
 o o o o . o o . o . o o o o o o . o o
o o . o o . o o o o . o o o . o o o o o
a(20) >= 42

Ecco il programma esteso:

import itertools
from ortools.sat.python import cp_model

class ReportPrinter(cp_model.CpSolverSolutionCallback):

    def __init__(self, n, total):
        cp_model.CpSolverSolutionCallback.__init__(self)
        self.__n = n
        self.__total = total

    def on_solution_callback(self):
        print('a(%d) >= %d' %
              (self.__n, self.__total-self.ObjectiveValue()) )

def solve(n):
    model = cp_model.CpModel()
    solver = cp_model.CpSolver()
    cells = {
        (y, x): model.NewBoolVar(str((y, x)))
        for y in range(n) for x in range(y + 1)}
    triangles = [
            {cells[v] for v in ((y1, x1), (y2, x1), (y2, x1 + y2 - y1))}
            for (y1, x1) in cells.keys() for y2 in range(y1 + 1, n)]
    for t1, t2 in itertools.combinations(triangles, 2):
        model.AddBoolOr(t1.symmetric_difference(t2))
    model.Minimize(sum(cells.values()))
    print('Solving n = %d:' % n)
    status = solver.SolveWithSolutionCallback(model, ReportPrinter(n,len(cells)))
    if status == cp_model.OPTIMAL:
        rel = '='
    elif status == cp_model.FEASIBLE:
        rel = '>='
    else:
        print('No result for a(%d)\n' % n)
        return
    for y in range(n):
        print(' '*(n-y-1), end='')
        for x in range(y+1):
            print('.o'[solver.Value(cells[(y,x)])],end=' ')
        print()
    print('a(%d) %s %d' % (n, rel, len(cells) - solver.ObjectiveValue()))
    print()

for n in itertools.count(2):
    solve(n)

1

Python 3

Basato fortemente sulla risposta di Delfad0r , segue principalmente la stessa progressione logica controllando le coppie di triangoli e convalidando la configurazione se non contiene coppie di triangoli che non superano questa convalida. Dal momento che non ho usato nessuna libreria oltre a itertools e copy, ho il pieno controllo sul salvataggio degli esempi incontrati nel programma.

examples = dict() # stores examples by key pair of n to a tuple with the triangle and number of lights turned off

for n in range(3, 8):
    tri = [] # model of the triangle, to be filled with booleans representing lights
    tri_points = [] # list of tuples representing points of the triangle
    for i in range(n):
        tri.append([True]*(i + 1))
        for j in range(i+1):
            tri_points.append((i, j))

    t_set = [] # list of all possible triangles from tri, represented by lists of points
    for i in range(n):
        for j in range(len(tri[i])):
            for k in range(1, n - i):
                t_set.append([(i, j), (i + k, j), (i + k, j + k)])

    from itertools import combinations
    import copy

    # validates whether or not a triangle of n lights can have i lights turned off, and saves an example to examples if validated
    def tri_validate(x):
        candidate_list = list(combinations(tri_points, x))
        tri_pairs = list(combinations(t_set, 2))
        for candidate in candidate_list:
            temp_tri = copy.deepcopy(tri)
            valid = False
            for point in candidate:
                (row, col) = point
                temp_tri[row][col] = False
            for pair in tri_pairs:
                valid = False
                (tri1, tri2) = pair
                for point in tri1:
                    if not valid:
                        if point not in tri2:
                            (row, col) = point
                            if temp_tri[row][col]:
                                valid = True
                for point in tri2:
                    if not valid:
                        if point not in tri1:
                            (row, col) = point
                            if temp_tri[row][col]:
                                valid = True
                if not valid:
                    break
            if valid:
                examples[n] = (temp_tri, x)
                return True
        return False

    # iterates up to the point that validation fails, then moves on to the next n
    for i in range(len(tri_points)):
        if tri_validate(i + 1):
            continue
        break

Il problema è che non è molto efficiente. Funziona molto velocemente n=5, ma inizia a rallentare significativamente oltre quel punto. A n=6, ci vuole circa un minuto per correre, ed è molto più lento a n=7. Immagino che ci siano molte correzioni di efficienza che possono essere fatte con questo programma, ma è una bozza di una buona soluzione rapidamente fatta con molta più flessibilità per verificare il funzionamento interno di questo metodo. Ci lavorerò progressivamente su questo nel tempo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.