Выигрышные стратегии для струнной строительной игры


14

Фон

Алиса и Боб играют в игру под названием « построить двоичное слово» . Для того, чтобы играть в эту игру, вы фиксируете длину n >= 0, набор Gиз длина- nдвоичных слов называется поставленная цель , и длина- nстроку , tсодержащую буквы Aи B, называемый порядок оборота . Игра длится по nочереди, и на ходу iигрок, определенный с помощью, t[i]выбирает немного w[i]. Когда игра заканчивается, игроки смотрят на двоичное слово, которое wони создали. Если это слово найдено в поставленной цели G, Алиса выигрывает игру; в противном случае Боб выигрывает.

Например, давайте исправить n = 4, G = [0001,1011,0010]и t = AABA. Алиса получает первый ход, и она выбирает w[0] = 0. Второй ход тоже за Алисой, и она выбирает w[1] = 0. У Боба третий ход, и он выбирает w[2] = 0. На последнем повороте Алиса выбирает w[3] = 1. Полученное слово, 0001находится в G, так что Алиса выигрывает игру.

Теперь, если бы Боб выбрал w[2] = 1, Алиса могла бы выбрать w[3] = 0в своем последнем повороте и все же выиграть. Это означает, что Алиса может выиграть игру независимо от того, как играет Боб. В этой ситуации у Алисы есть выигрышная стратегия . Эту стратегию можно представить в виде помеченного двоичного дерева, которое ветвится на уровнях, соответствующих поворотам Боба, и каждая ветвь которых содержит слово из G:

A A B A

-0-0-0-1
    \
     1-0

Алиса играет, просто следуя веткам на своем ходу; Независимо от того, какую ветвь выберет Боб, в конечном итоге победит Алиса.

вход

Вам дается в качестве входных данных длина n, а в Gкачестве (возможно, пустой) список строк длины n.

Выход

Ваш вывод - это список ордеров хода, для которых у Алисы есть выигрышная стратегия, что эквивалентно существованию бинарного дерева, как описано выше. Порядок ордеров хода не имеет значения, но дублирование запрещено.

Подробные правила

Вы можете написать полную программу или функцию. В случае программы вы можете выбрать разделитель для ввода и вывода, но он должен быть одинаковым для обоих. Самый короткий счетчик байтов выигрывает, и стандартные лазейки запрещены.

Тестовые случаи

3 [] -> []
3 [000,001,010,011,100,101,110,111] -> [AAA,AAB,ABA,ABB,BAA,BAB,BBA,BBB]
4 [0001,1011,0010] -> [AAAA,BAAA,AABA]
4 [0001,1011,0010,0110,1111,0000] -> [AAAA,BAAA,ABAA,BBAA,AABA,AAAB]
5 [00011,00110,00111,11110,00001,11101,10101,01010,00010] -> [AAAAA,BAAAA,ABAAA,BBAAA,AABAA,AAABA,BAABA,AAAAB,AABAB]

Интересный факт

Количество порядков хода на выходе всегда равно количеству слов в наборе целей.


5
Я довольно заинтригован тем фактом, что вход и выход имеют одинаковый размер. У вас есть доказательства или цитаты по этому факту? Интересно, есть ли способ вычислить эту функцию, которая интуитивно сохраняет размер.
xnor

2
Ваш тестовый пример № 5 противоречит вашему
забавному

3
@ mbomb007 случае Test # 5 списки 11101дважды; забавный факт все еще имеет место для наборов. Згарб, может вход содержит повторяющиеся элементы, или это ошибка?
xnor

@xnor Это то, что появилось в моем исследовании некоторое время назад. У меня есть доказательства в этом препринт , страница 16, но это, по существу , такой же , как ваша.
Згарб

1
@xnor Интуитивно понятно, что на любом ходу, если оба варианта равны 0 и 1, Алиса или Боб могут выбрать следующий ход. Если есть только один вариант выигрыша, Алиса должна выбрать следующий. Таким образом, количество вариантов для строки совпадает с количеством вариантов выигрышной стратегии. Вряд ли строгий, но убедительный.
Алхимик

Ответы:


1

Дьялог АПЛ, 59 байт

{(a≡,⊂⍬)∨0=⍴a←∪⍵:a⋄(∇h/t)(('A',¨∪),'B',¨∩)∇(~h←⊃¨a)/t←1↓¨a}

Тот же алгоритм, что и в решении @ xnor.

(a≡,⊂⍬)∨0=⍴a←∪⍵:a
           a←∪⍵    ⍝ "a" is the unique items of the argument
        0=⍴a       ⍝ is it empty?
 a≡,⊂⍬             ⍝ is it a vector that contains the empty vector?
       ∨       :a  ⍝ if any of the above, return "a"

(∇h/t)(('A',¨∪),'B',¨∩)∇(~h←⊃¨a)/t←1↓¨a
                                 t←1↓¨a  ⍝ drop an item from each of "a" and call that "t"
                         ~h←⊃¨a          ⍝ first of each of "a", call that "h", then negate it
                                /        ⍝ use "~h" as a boolean mask to select from "t"
                       ∇                 ⍝ apply a recursive call
(∇h/t)                                   ⍝ use "h" as a boolean mask on "t", then a recursive call
      (('A',¨∪),'B',¨∩)                  ⍝ apply a fork on the results from the two recursive calls:
       ('A',¨∪)                          ⍝   prepend 'A' to each of the intersection
               ,                         ⍝   concatenated with
                'B',¨∪                   ⍝   prepend 'B' to each of the union

13

Python, 132

def f(S,n):
 if n<1:return S
 a,b=[f({x[1:]for x in S if x[0]==c},n-1)for c in'01']
 return{'A'+y for y in a|b}|{'B'+y for y in a&b}

Пример выполнения:

f({'000','001','010','011','100','101','110','111'},3) == 
{'ABA', 'ABB', 'AAA', 'AAB', 'BBB', 'BBA', 'BAB', 'BAA'}

Это всего лишь игра в гольф, в основном, чтобы показать алгоритм. Входами и выходами являются наборы строк. Python, похоже, не обладает необходимыми возможностями для компактного выражения частей, поэтому было бы здорово, если бы кто-то написал это на более подходящем языке.

Вот как рекурсия может быть выражена математически. К сожалению, в PPCG по-прежнему отсутствует математический рендеринг, поэтому мне придется использовать блоки кода.

Объектами интереса являются наборы строк. Позвольте |представить набор союза и& представлять пересечение множества.

Если cэто символ, пусть c#Sпредставляет добавление символа cко всем строкам в S. И наоборот, пусть сокращение c\Sбудет строкой на один символ короче Sследующего за начальным символом c, например,0\{001,010,110,111} = {01,10} .

Мы можем уникально разделить набор строк Sс 01символами по их первому символу.

S = 0#(0\S) | 1#(1\S)

Затем мы можем выразить искомую функцию fследующим образом, с базовыми падежами в первых двух строках и рекурсивным банком в последней строке:

f({})   = {}
f({''}) = {''}
f(S)    = A#(f(0\S)|f(1\S)) | B#(f(0\S)&f(1\S))

Обратите внимание, что нам не нужно использовать длину n.

Почему это работает? Давайте подумаем о строках ходов, которые позволят Алисе победить за набор струн S.

Если первым персонажем является AАлиса, она может выбрать первый ход («0» или «1»), позволяя ей решить проблему до S0или S1. Итак, теперь оставшаяся строка перемещения должна быть хотя бы в одном из f(S0)или f(S1), следовательно, мы берем их объединение| .

Точно так же, если первый символ «B», Боб выбирает, и он выбирает худший для Алисы, поэтому оставшаяся строка движения должна быть в пересечении ( &).

Базовые случаи просто проверяют, Sпусто или нет в конце. Если мы отслеживаем длину строк n, вычитая 1 каждый раз, когда мы повторяем, вместо этого можно записать основы:

f(S) = S if n==0

Рекурсивное решение также объясняет забавный факт того f(S)же размера, что и S. Это верно для базовых случаев и для индуктивного случая

f(S) = A#(f(0\S)|f(1\S)) | B#(f(0\S)&f(1\S))

у нас есть

size(f(S)) = size(A#(f(0\S)|f(1\S)) | B#(f(0\S)&f(1\S)))
           = size(A#(f(0\S)|f(1\S))) + size(B#(f(0\S)&f(1\S))))
           = size((f(0\S)|f(1\S))) + size((f(0\S)&f(1\S))))
           = size(f(0\S)) + size(f(1\S))  [since size(X|Y) + size(X&Y) = size(X) + size(Y)]
           = size(0\S) + size(1\S)
           = size(S)

Запуск вашего кода дает TypeError: 'int' object is not subscriptable. У вас есть ссылка на работающую программу? Я просто вставил его и запустилprint f([0001,1011,0010],4)
mbomb007

@ mbomb007 Функция должна быть вызвана как f({'000','001','010','011','100','101','110','111'},3). Вы получаете ошибку таким образом?
xnor

Ах, я не видел, что я пропускал цитаты, спасибо. Он также работает сprint f(['0001','1011','0010'],4)
mbomb007

Если вы хотите запустить программу, зная nнезависимо от параметров, это будетn=len(S[0])if S!=[]else 0
mbomb007

Запустите его здесь: repl.it/7yI
mbomb007
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.