Видалення точок з трикутного масиву, не втрачаючи трикутників


17

У мене є проблема комбінаторики, яку я хотів би поставити на OEIS - проблема полягає в тому, що мені не вистачає термінів. Ця проблема із кодом - допомогти мені обчислити більше термінів, а переможцем стане користувач, який містить найбільшу кількість термінів.


Проблема

Припустимо, я даю вам трикутний масив лампочок з бічною довжиною н :

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

Я увімкну три лампочки, які утворюють «вертикальний» рівносторонній трикутник, як у наступному прикладі:

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

Перш ніж увімкнути ліхтарі, ваше завдання - зняти якомога більше лампочок із масиву - не втрачаючи можливості виводити трикутник, що ввімкнувся. Щоб було зрозуміло, якщо лампочка була знята, вона не горить, коли її положення увімкнено.

Наприклад, якщо ви вийняли наступні лампочки (позначені .), ви побачили б увімкнути лише два наступні світильники (позначені символом x), що досить однозначно вивести третє (не освітлене) положення:

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

Дозволяти a(n) буде максимальна кількість цибулин, яку можна вийняти, не вносячи жодних неоднозначностей.


Приклад

За допомогою наївного алгоритму я перевірив значення до трикутника довжиною сторони 7, як видно нижче:

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

Оцінка балів

Подання, що обчислює послідовність [a(2), a(3), ..., a(n)] для найбільшого n виграє. Якщо два подання мають однакові послідовності, виграє те, що було розміщено раніше.

Хоча це не потрібно для подання, мені було б повчально, якщо ви розміщуєте конструкцію отриманих трикутних масивів, як у наведеному вище прикладі.


1
Це не виклик коду, а не найшвидший код?
Дон Тисяча

6
Я думаю, ви повинні вибрати обмеження часу (скажімо, 60-ті), щоб конкурс не про те, скільки часу хтось витратив на виконання свого коду.
ділнан

Приємна проблема. Що ви маєте на увазі під «вертикальним» трикутником?
Демієн

Відповіді:


10

Пітон 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)))

Використовує вирішення CP-SAT Google OR-Tools .

Після запуску ~ 30 секунд він видає наступне:

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

n=9н6a(9)=15n=8 тому, що наразі обмеження в часі незрозумілі, але півгодини, мабуть, занадто довго.

Як це працює

Т1Т2Т1Т2

Таким чином, питання може бути переосмислене як проблема SAT, з одним обмеженням для кожної пари трикутників.

PS: Я дуже хотів би включити приклад n=8, але у мене виникають проблеми з рішенням SAT, який, очевидно, хоче зберегти рішення для себе.


Я вирішую перенести рішення до Mathematica , яке, на жаль, повільніше.
користувач202729

2

Отримання рішень з програми @ Delfad0r

Я розширив програму @ Delfad0r на вихідні рішення. Це також дає проміжні результати, тому ви отримуєте такий результат:

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

Це обчислення зайняло кілька годин.

Якщо ви нетерплячі і натискаєте Ctrl-Cпісля того, як було знайдено якесь неоптимальне рішення, програма покаже це рішення. Тому не потрібно багато часу, щоб отримати це:

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

Ось розширена програма:

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

Пітон 3

Грунтуючись сильно на відповіді Delfad0r , здебільшого слід той самий логічний прогрес, перевіряючи трикутникові пари та перевіряючи конфігурацію, якщо вона не містить пар трикутників, які б не виконали цю перевірку. Оскільки я не використовував жодної бібліотеки, крім itertools та копію, я маю повний контроль над збереженням прикладів, що зустрічаються у всій програмі.

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

Проблема в тому, що це не дуже ефективно. Він працює дуже швидко до n=5, але починає значно сповільнюватись через цю точку. На n=6пробіжку потрібно близько хвилини, і це набагато повільніше n=7. Я думаю, що існує багато виправлень ефективності, які можна зробити за допомогою цієї програми, але це швидко зроблений чорновий проект хорошого рішення з набагато більшою гнучкістю для перевірки внутрішньої роботи цього методу. Я буду з часом посилено працювати над цим.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.