Виграшні стратегії для грі в побудову струн


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 суперечить вашому цікавому факту ...
mbomb007

3
@ mbomb007 Тестовий випуск №5 перераховується 11101двічі; цікавий факт все ще стосується наборів. Згарб, може вхід містити повторювані елементи, чи це була помилка?
xnor

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

1
@xnor Інтуїтивно, на будь-якому кроці, якщо і 0, і 1 виграють вибір, то або Аліса, або Боб можуть вибрати наступний крок. Якщо є лише один варіант виграшу, то Аліса повинна вибрати наступний. Таким чином, кількість варіантів рядка така ж, як і кількість варіантів виграшної стратегії. Навряд чи суворий, але переконливий.
Алхімік

Відповіді:


1

Діалог APL, 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

Пітона, 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.