Belirli bir aralıktaki tüm sayıların XOR'unu bulun


99

"A" ve "b" nin tipik olarak 1 ile 4.000.000.000 arasında olabileceği geniş bir [a, b] aralığı verilir. Verilen aralıktaki tüm sayıların XOR'unu bulmanız gerekir.

Bu problem TopCoder SRM'de kullanıldı. Maçta sunulan çözümlerden birini gördüm ve nasıl çalıştığını anlayamıyorum.

Birisi kazanan çözümü açıklamaya yardımcı olabilir mi:

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);
}

Burada, getXor()geçirilen aralıktaki tüm sayının xor değerini hesaplamak için gerçek işlev [a, b] ve "f ()" bir yardımcı işlevdir.


Sorunuzu biraz düzenledim. Bir kodun nedenini açıklamayı önemsemiyoruz, ancak bunu çözmek için başka yolların yeni bir listesine ihtiyacımız yok. Bunu TopCoder'e bırakın.
Kev

@Kev Sorun yok! Bunu yazdım çünkü bazı insanlar önceden yazılmış olanı açıklamaktansa kendi yollarını vermeyi severler. Ve herhangi bir yeni fikir asla boşa
gitmez

Bunun için a<=0veya için tanımsız davranışı var b<0. long longişaretli bir tür olduğundan x%4, negatif girişler için negatif (veya 0) . Belki diziyi istiyor unsigned long longve / veya a & 3dizini indekslemek istiyorsunuz ?
Peter Cordes

Yanıtlar:


152

Bu oldukça zekice bir çözüm - çalışan XOR'larda bir sonuç modeli olduğu gerçeğinden yararlanıyor. f()Fonksiyon XOR toplam çalışma [0, a] hesaplar. 4 bitlik sayılar için bu tabloya bir göz atın:

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]

İlk sütunun ikili gösterim olduğu ve ardından ondalık sonuç ve bunun XOR listesine indeksi (a) ile ilişkisi olduğu yerde. Bunun nedeni, tüm üst bitlerin birbirini tutması ve en düşük iki bitin her 4'te döngü yapmasıdır. Yani, o küçük arama tablosuna bu şekilde ulaşılır.

Şimdi, [a, b] 'nin genel bir aralığını düşünün. f()[0, a-1] ve [0, b] için XOR'u bulmak için kullanabiliriz . Kendi başına XOR'lu herhangi bir değer sıfır olduğundan, f(a-1)sadece XOR'daki tüm değerleri iptal aederek, [a, b] aralığının XOR'unu bırakır.


minimum aralık eşiği 1, 0 değil
Pencho Ilchev

2
@PenchoIlchev 0 içerip içermemesi biraz tartışmalı - (n ^ 0) == n
FatalError

2
@ rajneesh2k10 4'lük serilerde (4'ün katından başlayarak), en düşük hariç tüm bitler aynıdır, bu nedenle birbirlerini iptal etmek veya orijinal değerlerine sahip olmak arasında gidip gelirler. En düşük bitin her 2'de bir döngü yaptığı doğrudur, ancak 0 ^ 1 == 1 (yani iptal etmezler). En alttaki ikinin özel olmasının nedeni (00 ^ 01 ^ 10 ^ 11) == 00'dır. Diğer bir deyişle, üzerinden geçtiğiniz her 4 değer sizi 0'a geri götürür ve böylece tüm bu döngüleri iptal edebilirsiniz. neden% 4 önemli.
FatalError

3
@Pandrei aorada 2 değil, 0 değil.
harold

1
Bu sütun çalışan xor ve 1 xor 2 3'tür, dolayısıyla bu satırdaki mevcut değer bana doğru görünüyor.
FatalError

58

FatalError'ın harika cevabına ek olarak, satır return f(b)^f(a-1);daha iyi açıklanabilir. Kısacası, bunun nedeni XOR'un şu harika özelliklere sahip olmasıdır:

  • Bu var çağrışımlı - Yer parantez nereye istersen
  • Bu var değişmeli - vasıta etrafında operatörleri taşıyabilirsiniz (onlar "gidip")

İşte her ikisi de iş başında:

(a ^ b ^ c) ^ (d ^ e ^ f) = (f ^ e) ^ (d ^ a ^ b) ^ c
  • Kendini tersine çevirir

Bunun gibi:

a ^ b = c
c ^ a = b

Topla ve çarp, diğer ilişkilendirmeli / değişmeli operatörlere iki örnektir, ancak kendilerini tersine çevirmezler. Tamam, peki, bu özellikler neden önemli? Pekala, basit bir yol, onu gerçekte ne olduğu konusunda genişletmektir ve sonra bu özellikleri işte görebilirsiniz.

Önce ne istediğimizi tanımlayalım ve n diyelim:

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

Eğer yardımı olacaksa, XOR (^) 'u bir toplamaymış gibi düşünün.

İşlevi de tanımlayalım:

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

bdeğerinden büyüktür a, bu yüzden birkaç ekstra parantez içine güvenli bir şekilde bırakarak (bunu ilişkilendirebiliriz çünkü yapabiliriz), şunu da söyleyebiliriz:

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

Aşağıdakileri basitleştirir:

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

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

Sonra, bize sihirli çizgiyi vermek için bu ters özelliği ve değişme özelliğini kullanıyoruz:

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

XOR'u bir toplama gibi düşünüyor olsaydınız, orada bir çıkarmaya düşerdiniz. XOR, XOR için ne eklenecekse!

Bunu kendim nasıl bulabilirim?

Mantıksal operatörlerin özelliklerini hatırlayın. Onlarla neredeyse bir toplama veya çarpma gibi çalışın. (&), Xor (^) ve veya (|) çağrışımsal olmaları alışılmadık geliyor ama onlar!

İlk önce saf uygulamayı çalıştırın, çıktıdaki kalıpları arayın, ardından modelin doğru olduğunu onaylayan kuralları bulmaya başlayın. Uygulamanızı daha da basitleştirin ve tekrarlayın. Muhtemelen orijinal yaratıcının izlediği yol budur ve tamamen optimal olmadığı gerçeğiyle vurgulanır (yani bir dizi yerine bir anahtar ifadesi kullanın).


3
Bu bana geçen sene üniversitede aldığım Kesikli Matematik dersimi hatırlattı. Eğlenceli günler. Bunu okuduktan hemen sonra aklıma gelen bu XKCD çizgi romanı .
Sean Francis N. Ballais

3

Aşağıdaki kodun da soruda verilen çözüm gibi çalıştığını öğrendim.

Belki bu biraz optimize edilmiş olabilir, ancak kabul edilen cevapta verildiği gibi tekrarları gözlemlemekten elde ettiğim şey tam olarak

@Luke Briggs'in yanıtında açıklandığı gibi, verilen kodun ardındaki matematiksel kanıtı bilmek / anlamak istiyorum

İşte o JAVA kodu

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

Problemi özyineleme ile çözdüm. Veri kümesini her yineleme için neredeyse eşit bir parçaya bölerim.

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;
}

Çözüm hakkındaki düşüncelerinizi bana bildirin. İyileştirme geri bildirimleri almaktan mutluluk duyarız. Önerilen çözüm, XOR'u 0 (log N) karmaşıklığında hesaplar.

teşekkür ederim


Bu, normal m ^ (m + 1) ^ ... ^ (n-1) ^ n hesaplamasıyla aynı Hesaplama karmaşıklığına sahiptir. Bu 0 (n).
Thế Anh Nguyenễn

0

XOR'u 0'dan N'ye desteklemek için verilen kodun aşağıdaki gibi değiştirilmesi gerekiyordu,

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);
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.