Removendo pontos de uma matriz triangular sem perder triângulos

17

Eu tenho um problema combinatório que gostaria de colocar no OEIS - o problema é que não tenho termos suficientes. Esse desafio de código é ajudar-me a calcular mais termos, e o vencedor será o usuário com o envio contendo o maior número de termos.


O problema

Suponha que eu lhe forneça uma matriz triangular de lâmpadas com comprimento lateral :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

Vou acender três lâmpadas que formam um triângulo equilátero "vertical", como no exemplo a seguir:

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

Antes de acender as luzes, seu trabalho é remover o maior número possível de lâmpadas da matriz - sem perder a capacidade de deduzir o triângulo de lâmpadas que foram acesas. Para ficar claro, se uma lâmpada foi removida, ela não acende quando sua posição é ligada.

Por exemplo, se você removesse as seguintes lâmpadas (marcadas por .), você apenas veria as duas luzes seguintes acesas (marcadas por x), o que é suficiente para deduzir exclusivamente a terceira posição (apagada):

     .              .
    . 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

Seja a(n)o número máximo de lâmpadas que podem ser removidas sem introduzir ambiguidades.


Exemplo

Com um algoritmo ingênuo, verifiquei valores até um triângulo com o comprimento lateral 7, como mostrado abaixo:

                                                                      .
                                                      .              . 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

Pontuação

A submissão que calcula a sequência [a(2), a(3), ..., a(n)]para os maiores n ganhos. Se dois envios tiverem seqüências idênticas, o que foi postado anteriormente vence.

Embora não seja necessário para a apresentação, seria instrutivo para mim se você postar uma construção das matrizes triangluar resultantes, como no exemplo acima.

Peter Kagey
fonte
1
Isso não é um desafio ao código, e não um código mais rápido?
Don Thousand
6
Acho que você deve escolher um limite de tempo (por exemplo, 60 anos) para que o concurso não seja por quanto tempo alguém gastou executando seu código.
precisa saber é o seguinte
Bom problema. O que você quer dizer com triângulo "vertical"?
Damien

Respostas:

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)))

Usa o solucionador de CP-SAT do Google OR-Tools .

Depois de executar por ~ 30 segundos, ele gera o seguinte:

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

Eu não tentei esperar n=9, pois provavelmente levaria horas (o número de restrições cresce como )n6 . Após menos de 30 minutos de computação, descobri isso a(9)=15. Estou deixando minha pontuação como n=8porque, no momento, as restrições de tempo não são claras, mas meia hora provavelmente é muito longa.

Como funciona

Tome dois triângulos equilaterais distintos e . Para evitar ambiguidade, deve haver pelo menos uma lâmpada em um vértice pertencendo exatamente a um de e .T 2 T 1 T 2T1T2T1T2

Assim, a questão pode ser reformulada como um problema de SAT, com uma restrição para cada par de triângulos.

PS: Gostaria muito de incluir um exemplo n=8, mas estou tendo problemas com o solucionador SAT, que aparentemente deseja manter as soluções por si só.

Delfad0r
fonte
Decido portar a solução para o Mathematica , que infelizmente é mais lento.
user202729
2

Obtendo as soluções do programa @ Delfad0r

Estendi o programa da @ Delfad0r para soluções de saída. Também fornece resultados intermediários, para que você obtenha uma saída como esta:

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

Este cálculo levou várias horas.

Se você ficar impaciente e pressionar Ctrl-Capós encontrar alguma solução possivelmente não ótima, o programa mostrará essa solução. Portanto, não demora muito para conseguir isso:

                   .
                  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

Aqui está o programa estendido:

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)
Peneiradores cristãos
fonte
1

Python 3

Baseado fortemente na resposta de Delfad0r , segue principalmente a mesma progressão lógica verificando pares de triângulos e validando a configuração se ela não contiver pares de triângulos que falhem nessa validação. Como não usei nenhuma biblioteca além de ferramentas e cópias, tenho controle total sobre os exemplos encontrados ao longo do programa.

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

O problema é que não é muito eficiente. Ele roda muito rápido n=5, mas começa a desacelerar significativamente além desse ponto. Às n=6, leva cerca de um minuto para ser executado, e é muito mais lento n=7. Imagino que existem muitas correções de eficiência que podem ser feitas com este programa, mas é um rascunho rápido de uma boa solução com muito mais flexibilidade para verificar o funcionamento interno desse método. Vou trabalhar gradualmente nisso com o tempo.

TCFP
fonte