Estrategias ganadoras para un juego de construcción de cuerdas

14

Antecedentes

Alice y Bob juegan un juego llamado construir una palabra binaria . Para jugar el juego, arreglas una longitud n >= 0, un conjunto Gde npalabras binarias de longitud llamadas meta fijadas y una ncadena de longitud que tcontiene las letras Ay B, llamada orden de turno . El juego dura nturnos, y en turno i, el jugador definido por t[i]selecciona un poco w[i]. Cuando termina el juego, los jugadores miran la palabra binaria wque han construido. Si esta palabra se encuentra en el objetivo establecido G, Alice gana el juego; de lo contrario, Bob gana.

Por ejemplo, la solución deje n = 4, G = [0001,1011,0010]y t = AABA. Alice obtiene el primer turno, y ella elige w[0] = 0. El segundo turno también es de Alice, y ella elige w[1] = 0. Bob tiene el tercer turno, y él elige w[2] = 0. En el turno final, Alice elige w[3] = 1. La palabra resultante 0001, se encuentra en G, por lo que Alice gana el juego.

Ahora, si Bob hubiera elegido w[2] = 1, Alice podría haber elegido w[3] = 0en su turno final, y aún así ganar. Esto significa que Alice puede ganar el juego sin importar cómo juegue Bob. En esta situación, Alice tiene una estrategia ganadora . Esta estrategia se puede visualizar como un árbol binario etiquetado, que se ramifica en los niveles correspondientes a los turnos de Bob, y cuyas ramas contienen una palabra de G:

A A B A

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

Alice juega simplemente siguiendo las ramas en su turno; no importa qué rama elija Bob, Alice finalmente gana.

Entrada

Se le da como entrada la longitud ny el conjunto Gcomo una lista (posiblemente vacía) de cadenas de longitud n.

Salida

Su salida es la lista de órdenes de turno para las cuales Alice tiene una estrategia ganadora, que es equivalente a la existencia de un árbol binario como se describió anteriormente. El orden de las órdenes de turno no importa, pero los duplicados están prohibidos.

Reglas detalladas

Puede escribir un programa completo o una función. En el caso de un programa, puede elegir el delimitador para la entrada y la salida, pero debe ser el mismo para ambos. El conteo de bytes más corto gana, y las lagunas estándar no se permiten.

Casos de prueba

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]

Hecho de la diversión

El número de órdenes de turno en la salida siempre es igual al número de palabras en el conjunto de objetivos.

Zgarb
fuente
55
Estoy bastante intrigado por el hecho de que la entrada y la salida tienen el mismo tamaño. ¿Tiene una prueba o cita para este hecho? Me pregunto si hay una manera de calcular esta función que conserva intuitivamente el tamaño.
xnor
2
Su caso de prueba # 5 contradice su hecho divertido ...
mbomb007
3
@ mbomb007 El caso de prueba n. ° 5 aparece 11101dos veces; El hecho divertido sigue siendo válido para los sets. Zgarb, ¿puede la entrada contener elementos repetidos o fue un error?
xnor
@xnor Esto es algo que surgió en mi investigación hace un tiempo. Tengo una prueba en este preprint , página 16, pero es esencialmente la misma que la suya.
Zgarb
1
@xnor Intuitivamente, en cualquier turno, si tanto 0 como 1 son elecciones ganadoras, entonces Alice o Bob pueden elegir el siguiente movimiento. Si solo hay una opción ganadora, entonces Alice debe elegir la siguiente. Por lo tanto, el número de opciones para la cadena es el mismo que el número de opciones de estrategia ganadora. Apenas riguroso, pero convincente.
Alquimista

Respuestas:

1

Dyalog APL, 59 bytes

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

Mismo algoritmo que en la solución de @ 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
ngn
fuente
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}

Ejemplo de ejecución:

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

Esto es solo una especie de golf, principalmente para mostrar el algoritmo. Las entradas y salidas son conjuntos de cadenas. Python no parece tener las características correctas para expresar partes de esto de manera compacta, por lo que sería genial si alguien escribiera esto en un lenguaje más adecuado.

Así es como la recursividad se puede expresar matemáticamente. Desafortunadamente, PPCG todavía carece de representación matemática, por lo que tendré que usar bloques de código.

Los objetos de interés son conjuntos de cadenas. Deje |representar conjunto de unión y& represent set set intersection.

Si ces un carácter, c#Srepresente anteponer el carácter ca todas las cadenas S. Por el contrario, deje que la contracción c\Ssea ​​la cadena de un carácter más corta Sque sigue al carácter inicial c, por ejemplo,0\{001,010,110,111} = {01,10} .

Podemos dividir de manera única un conjunto de cadenas Scon caracteres 01por su primer personaje.

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

Luego, podemos expresar la función deseada de la fsiguiente manera, con los casos base en las dos primeras líneas y la lata recursiva en la última línea:

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

Tenga en cuenta que no necesitamos usar la longitud n.

¿Por qué funciona esto? Pensemos en las cadenas de movimiento que permiten que Alice gane por un conjunto de cadenas S.

Si el primer personaje es A, Alice puede elegir el primer movimiento ('0' o '1'), permitiéndole elegir reducir el problema a S0o S1. Entonces, la cadena de movimiento restante debe estar en al menos uno de f(S0)o f(S1), por lo tanto, tomamos su unión| .

Del mismo modo, si el primer personaje es 'B', Bob elige, y elegirá el peor para Alice, por lo que la cadena de movimiento restante debe estar en la intersección ( &).

Los casos base simplemente comprueban si Sestá vacío o no al final. Si estamos rastreando la longitud de las cadenas n, restando 1 cada vez que repetimos, las bases pueden escribirse en su lugar:

f(S) = S if n==0

La solución recursiva también explica el hecho divertido que f(S)tiene el mismo tamaño que S. Es cierto para los casos base y para el caso inductivo

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

tenemos

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)
xnor
fuente
Ejecutar su código da TypeError: 'int' object is not subscriptable. ¿Tiene un enlace a un programa ejecutable? Me acaba de pegar y corrí conprint f([0001,1011,0010],4)
mbomb007
@ mbomb007 La función debe invocarse como f({'000','001','010','011','100','101','110','111'},3) . ¿Recibes un error de esta manera?
xnor
Ah, no vi que me faltaban citas, gracias. También funciona conprint f(['0001','1011','0010'],4)
mbomb007
Si desea ejecutar el programa con nindependencia de los parámetros, serían=len(S[0])if S!=[]else 0
mbomb007
Ejecútelo aquí: repl.it/7yI
mbomb007