Trova XOR di tutti i numeri in un dato intervallo


99

Viene fornito un ampio intervallo [a, b] dove "a" e "b" possono essere generalmente compresi tra 1 e 4.000.000.000 inclusi. Devi scoprire lo XOR di tutti i numeri nell'intervallo dato.

Questo problema è stato utilizzato in TopCoder SRM. Ho visto una delle soluzioni presentate nella partita e non riesco a capire come funziona.

Qualcuno potrebbe aiutare a spiegare la soluzione vincente:

long long f(long long a) {
     long long res[] = {a,1,a+1,0};
     return res[a%4];
}

long long getXor(long long a, long long b) {
     return f(b)^f(a-1);
}

Ecco getXor()la funzione effettiva per calcolare lo xor di tutti i numeri nell'intervallo passato [a, b] e "f ()" è una funzione di supporto.


Ho modificato leggermente la tua domanda. Non ci interessa spiegare il motivo di alcuni codici, ma non abbiamo bisogno di un nuovo elenco di altri modi per risolverlo. Lascialo a TopCoder.
Kev

@Kev Nessun problema! L'ho scritto perché alcune persone amano dare la propria strada piuttosto che spiegare le cose già scritte. E ogni nuova idea non è mai uno spreco ...;)
rajneesh2k10

Questo ha un comportamento indefinito per a<=0o per b<0. long longè un tipo con segno, quindi x%4è negativo (o 0) per gli input negativi . Forse vuoi unsigned long longe / o a & 3indicizzare l'array?
Peter Cordes

Risposte:


152

Questa è una soluzione piuttosto intelligente: sfrutta il fatto che esiste uno schema di risultati negli XOR in esecuzione. La f()funzione calcola la corsa totale XOR da [0, a]. Dai un'occhiata a questa tabella per i numeri a 4 bit:

0000 <- 0  [a]
0001 <- 1  [1]
0010 <- 3  [a+1]
0011 <- 0  [0]
0100 <- 4  [a]
0101 <- 1  [1]
0110 <- 7  [a+1]
0111 <- 0  [0]
1000 <- 8  [a]
1001 <- 1  [1]
1010 <- 11 [a+1]
1011 <- 0  [0]
1100 <- 12 [a]
1101 <- 1  [1]
1110 <- 15 [a+1]
1111 <- 0  [0]

Dove la prima colonna è la rappresentazione binaria e quindi il risultato decimale e la sua relazione con il suo indice (a) nell'elenco XOR. Questo accade perché tutti i bit superiori si annullano e i due bit più bassi ciclano ogni 4. Quindi, ecco come arrivare a quella piccola tabella di ricerca.

Consideriamo ora un intervallo generale di [a, b]. Possiamo usare f()per trovare l'XOR per [0, a-1] e [0, b]. Poiché qualsiasi valore XOR di se stesso è zero, f(a-1)cancella semplicemente tutti i valori in XOR eseguiti inferiori a a, lasciandoti con lo XOR dell'intervallo [a, b].


la soglia del range minimo è 1, non 0
Pencho Ilchev

2
@PenchoIlchev Che includa o meno 0 è un po 'discutibile - (n ^ 0) == n
FatalError

2
@ rajneesh2k10 Bene, in serie di 4 (a partire da un multiplo di 4), tutti i bit tranne il più basso sono uguali, quindi si alternano tra l'annullamento a vicenda o il loro valore originale. È vero che il bit più basso va in ciclo ogni 2, ma 0 ^ 1 == 1 (cioè non si annullano). Il motivo per cui i due più bassi sono speciali è perché (00 ^ 01 ^ 10 ^ 11) == 00. In altre parole, ogni 4 valori che attraversi ti riporta a 0, quindi puoi annullare tutti questi cicli, che è perché un% 4 è significativo.
FatalError

3
@Pandrei ac'è 2, non 0.
Harold

1
Quella colonna è la xor in esecuzione e 1 xor 2 è 3, quindi il valore corrente in quella riga mi sembra corretto.
FatalError

58

Aggiungendo alla grande risposta di FatalError, la linea return f(b)^f(a-1);potrebbe essere spiegata meglio. In breve, è perché XOR ha queste meravigliose proprietà:

  • È associativo : posiziona le parentesi dove vuoi
  • È commutativo : ciò significa che puoi spostare gli operatori (possono "spostarsi")

Ecco entrambi in azione:

(a ^ b ^ c) ^ (d ^ e ^ f) = (f ^ e) ^ (d ^ a ^ b) ^ c
  • Si inverte

Come questo:

a ^ b = c
c ^ a = b

Somma e moltiplica sono due esempi di altri operatori associativi / commutativi, ma non si invertono. Ok, allora, perché queste proprietà sono importanti? Bene, un percorso semplice è espanderlo in quello che è realmente, e poi puoi vedere queste proprietà all'opera.

Per prima cosa, definiamo ciò che vogliamo e chiamiamolo n:

n      = (a ^ a+1 ^ a+2 .. ^ b)

Se aiuta, pensa a XOR (^) come se fosse un'aggiunta.

Definiamo anche la funzione:

f(b)   = 0 ^ 1 ^ 2 ^ 3 ^ 4 .. ^ b

bè maggiore di a, quindi semplicemente inserendo in modo sicuro alcune parentesi extra (cosa che possiamo fare perché è associativa), possiamo anche dire questo:

f(b)   = ( 0 ^ 1 ^ 2 ^ 3 ^ 4 .. ^ (a-1) ) ^ (a ^ a+1 ^ a+2 .. ^ b)

Che si semplifica a:

f(b)   = f(a-1) ^ (a ^ a+1 ^ a+2 .. ^ b)

f(b)   = f(a-1) ^ n

Successivamente, usiamo la proprietà di inversione e la commutività per darci la linea magica:

n      = f(b) ^ f(a-1)

Se stavi pensando a XOR come un add, avresti fatto cadere una sottrazione lì. XOR sta a XOR ciò che aggiungere è sottrarre!

Come posso arrivare a questo da solo?

Ricorda le proprietà degli operatori logici. Lavora con loro quasi come aggiungere o moltiplicare se aiuta. Sembra insolito che and (&), xor (^) e or (|) siano associativi, ma lo sono!

Esegui prima l'implementazione ingenua, cerca i modelli nell'output, quindi inizia a trovare le regole che confermano che il modello è vero. Semplifica ulteriormente la tua implementazione e ripeti. Questo è probabilmente il percorso intrapreso dal creatore originale, evidenziato dal fatto che non è del tutto ottimale (cioè usa un'istruzione switch piuttosto che un array).


3
Questo mi ricorda il mio corso di matematica discreta che ho seguito l'anno scorso all'università. Giorni divertenti. Quello che mi è venuto in mente subito dopo aver letto questo è questo fumetto XKCD .
Sean Francis N. Ballais

3

Ho scoperto che anche il codice seguente funziona come la soluzione fornita nella domanda.

Potrebbe essere un po 'ottimizzato, ma è proprio quello che ho ottenuto osservando la ripetizione come data nella risposta accettata,

Vorrei conoscere / capire la prova matematica dietro il codice dato, come spiegato nella risposta di @Luke Briggs

Ecco quel codice JAVA

public int findXORofRange(int m, int n) {
    int[] patternTracker;

    if(m % 2 == 0)
        patternTracker = new int[] {n, 1, n^1, 0};
    else
        patternTracker = new int[] {m, m^n, m-1, (m-1)^n};

    return patternTracker[(n-m) % 4];
}

2

Ho risolto il problema con la ricorsione. Divido semplicemente il set di dati in una parte quasi uguale per ogni iterazione.

public int recursion(int M, int N) {
    if (N - M == 1) {
        return M ^ N;
    } else {
        int pivot = this.calculatePivot(M, N);
        if (pivot + 1 == N) {
            return this.recursion(M, pivot) ^ N;
        } else {
            return this.recursion(M, pivot) ^ this.recursion(pivot + 1, N);
        }
    }
}
public int calculatePivot(int M, int N) {
    return (M + N) / 2;
}

Fammi sapere i tuoi pensieri sulla soluzione. Felice di ricevere feedback sui miglioramenti. La soluzione proposta calcola lo XOR in complessità 0 (log N).

Grazie


Questo ha la stessa complessità computazionale con il normale calcolo m ^ (m + 1) ^ ... ^ (n-1) ^ n. Questo è 0 (n).
Thế Anh Nguyễn

0

Per supportare XOR da 0 a N il codice fornito doveva essere modificato come di seguito,

int f(int a) {
    int []res = {a, 1, a+1, 0};
    return res[a % 4];
}

int getXor(int a, int b) {
    return f(b) ^ f(a);
}
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.