Strategia ottimale per un gioco astratto


12

Mi è stato dato il seguente problema in un'intervista (che non ho già risolto, non cercando di imbrogliare): Il gioco inizia con un numero intero positivo . (Ad esempio A 0 = 1234. ) Questo numero viene convertito in rappresentazione binaria e N è il numero di bit impostato su 1 . (Ad esempio A 0 = b 100 1101 0010 , N = 5. )A0A0=1234N1A0=b100 1101 0010N=5.

Il giocatore 1 sceglie un numero minore di A 0 . B 0 deve avere solo un bit impostato su 1. (Ad es. B 0 = b 10 0000 0000 = 512 ). Sia A 1 = A 0 - B 0 . (Ad esempio A 1 = 1234 - 512 = 722 = b 10 1101 0010. ) Una mossa è valida se B 0B0A0B0B0=b10 0000 0000=512A1=A0B0A1=1234512=722=b1011010010B0soddisfa i vincoli precedenti, e se il numero di bit impostati in è ancora pari a N .A1

Il giocatore 2 continua da scegliendo un B 1 valido , quindi il giocatore 1 continua da A 2 e così via. Un giocatore perde se non gli rimangono mosse valide.A1B1A2

Supponendo che entrambi i giocatori giochino in modo ottimale, determinare il giocatore vincente usando un metodo ragionevolmente efficiente. (Nella mia definizione del problema, i vincoli su questo erano che il programma doveva essere in grado di fornire una soluzione per alcuni milioni di numeri di input che si adattavano a un intero con segno a 32 bit.) Cioè, la soluzione non deve essere completamente analitico.


Il mio interesse personale qui è capire se l'aspettativa di me di trovare e implementare la soluzione corretta senza alcun feedback sulla correttezza nei 120 minuti che mi è stato dato era ragionevole; o se questa era una di quelle domande "vediamo se hanno già visto questo puzzle prima".

Ho fallito perché ho scelto di attuare quella che sembrava una strategia ragionevole, che mi ha dato risultati corretti per i pochi casi di test che mi sono stati dati in anticipo, ho sprecato troppo tempo a farlo correre veloce e ho finito per consegnare in modo errato piena resa allo scadere del tempo.

Con il senno di poi avrei dovuto implementare una ricerca della forza bruta e memorizzare soluzioni parziali per piccoli numeri di partenza, ma il senno di poi è sempre 20/20. Sono curioso, tuttavia, se esiste un approccio comune diverso che mi ha eluso come flunkee.


Dalla descrizione non ho capito che le mosse scelte devono avere un singolo bit impostato su 1 (ho pensato che fosse solo una parte dell'esempio).
jjmontes,

@jjmontes - È la prima regola per scegliere un numero B - tutti gli esempi sono indicati come tali, tutto al di fuori delle parentesi è generale. Hai un suggerimento su come avrebbe potuto essere più chiaro?
millimoose,

1
Forse "Il giocatore 1 sceglie un numero minore di A 0 , che deve avere solo un bit impostato su 1."? (forse sono stato solo io, ma ho dovuto leggere la risposta @orlp per rendermi conto che questo era un vincolo). B0A0
jjmontes,

@Veedrac - Amico, lo avevo saputo, tutti i miei sforzi fatti per far correre i bitcount ragionevolmente velocemente non sarebbero stati uno spreco. Una risposta che spiega perché funziona sarebbe eccellente.
millimoose,

@millimoose Bitcount è in hardware per la maggior parte delle CPU moderne!
Veedrac,

Risposte:


21

011001

1001

Quindi l'unico fattore determinante in un gioco è quanti swap ci vogliono per arrivare allo stato in cui tutti sono sulla destra e non c'è strategia vincente o perdente. La parità del numero di swap necessari è l'unico fattore determinante.

1

i1101kki

i = 0
k = 0
total = 0
while n > 0:
    if n & 1:
       total += k - i
       i += 1
    n >>= 1
    k += 1

totalO(logn)


Sembra giusto, mi sono imbattuto in frammenti di questo approccio dopo la consegna. Immagino di aver finito saltando la pistola quando iniziavo a scrivere codice e rimanevo impantanato nel conseguente inseguimento dell'oca selvatica.
millimoose,

Pensando a questo, il fatto che la strategia non abbia importanza significa che o ho un bug piuttosto oscuro nella mia implementazione, o avrebbe dovuto produrre gli stessi risultati di qualsiasi altra implementazione che gioca correttamente il gioco ...
millimoose

5

Un modo per risolvere un simile problema è il seguente:

  • Trova la soluzione per alcuni semplici valori usando l'approccio "forza bruta memorizzata" che stai suggerendo.

  • Indovina la risposta (quali posizioni stanno vincendo e quali stanno perdendo).

  • Prova a provare la tua risposta. Se ci riesci, bene. Altrimenti, prova a trovare un controesempio e usalo per indovinare un'altra risposta. Qui potrebbe essere utile risolvere qualche altro caso.

È davvero difficile dire quanto tempo ci vuole. Tuttavia, nelle interviste non è necessario trovare la soluzione. Piuttosto, gli intervistatori vogliono sapere come ti sei avvicinato per risolvere il problema e quali progressi sei riuscito a fare.


Sì no, mi hanno rifiutato perché la mia uscita era sbagliata e avevo esaurito il tempo.
millimoose,

L'approccio alla forza bruta memorizzato sarebbe stato corretto, poiché non ci sono scorciatoie per quanto riguarda la strategia. Tuttavia, anche, immaginavo, sarebbe insopportabilmente lento, e la memoizzazione avrebbe potuto essere troppo impegnativa per non avere molto aiuto senza usare sciocche quantità di memoria. O forse no, ci proverò più tardi solo per cancellare questo dal mio sistema.
millimoose,

5

Nota dalla risposta di @ orlp che vogliamo la parità della somma degli spostamenti dalla posizione iniziale alla posizione finale. Annotiamo questo:

       9876543210
       9 76 4  1    (positions at start)
start: 1011010010
end:   0000011111
            43210   (positions at end)

Quindi vogliamo

  ((1 - 0) + (4 - 1) + (6 - 2) + (7 - 3) + (9 - 4)) & 1
= ((1 + 4 + 6 + 7 + 9) - (0 + 1 + 2 + 3 + 4)) & 1
= ((1 + 0 + 0 + 1 + 1) - (0 + 1 + 0 + 1 + 0)) & 1

La prima parte è solo la parità del numero di bit nelle posizioni dispari. Puoi mascherarlo prendendo il numero intero senza segno massimo, dividendolo per 0b11 e negando.

= (bitcount(x & ~(UINT_MAX / 0b11)) ^ (0 + 1 + 0 + 1 + 0)) & 1

La seconda parte è la parità della metà del numero di bit in x.

= (bitcount(x & ~(UINT_MAX / 0b11)) ^ (bitcount(x) >> 1)) & 1

bitcountpuò utilizzare le popcntistruzioni hardware o può essere implementato manualmente utilizzando solo l'ultimo o il penultimo bit, con riduzioni veloci come questa .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.