Tìm XOR của tất cả các số trong một phạm vi nhất định


99

Bạn được cung cấp một phạm vi lớn [a, b] trong đó 'a' và 'b' thường có thể nằm trong khoảng từ 1 đến 4.000.000.000. Bạn phải tìm ra XOR của tất cả các số trong phạm vi đã cho.

Sự cố này đã được sử dụng trong TopCoder SRM. Tôi đã thấy một trong những giải pháp được đưa ra trong trận đấu và tôi không thể tìm ra cách hoạt động của nó.

Ai đó có thể giúp giải thích giải pháp chiến thắng:

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

Đây, getXor()là hàm thực tế để tính xor của tất cả các số trong phạm vi đã truyền [a, b] và "f ()" là một hàm trợ giúp.


Tôi đã chỉnh sửa câu hỏi của bạn chỉ một chút. Chúng tôi không ngại giải thích lý do tại sao của một số mã, nhưng chúng tôi không cần danh sách mới về các cách khác để giải quyết vấn đề này. Hãy giao việc đó cho TopCoder.
Kev

@Kev Không có vấn đề gì! Tôi viết điều đó vì một số người thích đưa ra cách riêng của họ hơn là giải thích những điều đã được viết sẵn. Và bất kỳ ý tưởng mới nào cũng không bao giờ là lãng phí ...;)
rajneesh2k10

Điều này có hành vi không xác định cho a<=0hoặc cho b<0. long longlà một loại có dấu, vì vậy x%4là âm (hoặc 0) cho các đầu vào âm . Có lẽ bạn muốn unsigned long longvà / hoặc a & 3lập chỉ mục mảng?
Peter Cordes

Câu trả lời:


152

Đây là một giải pháp khá thông minh - nó khai thác thực tế là có một mẫu kết quả trong các XOR đang chạy. Các f()chức năng tính toán tổng XOR chạy từ [0, a]. Hãy xem bảng này để biết các số 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]

Trong đó cột đầu tiên là biểu diễn nhị phân và sau đó là kết quả thập phân và mối quan hệ của nó với chỉ số (a) của nó trong danh sách XOR. Điều này xảy ra bởi vì tất cả các bit trên đều hủy bỏ và hai bit thấp nhất có chu kỳ mỗi 4. Vì vậy, đó là cách đi đến bảng tra cứu nhỏ đó.

Bây giờ, hãy xem xét một phạm vi tổng quát của [a, b]. Chúng ta có thể sử dụng f()để tìm XOR cho [0, a-1] và [0, b]. Vì bất kỳ giá trị nào XOR'd với chính nó là 0, nên f(a-1)chỉ loại bỏ tất cả các giá trị trong XOR chạy ít hơn a, để lại cho bạn XOR của phạm vi [a, b].


ngưỡng phạm vi tối thiểu là 1, không phải 0
Pencho Ilchev

2
@PenchoIlchev Việc nó có bao gồm 0 hay không là một loại tranh luận - (n ^ 0) == n
FatalError

2
@ rajneesh2k10 Vâng, trong khoảng thời gian 4 (bắt đầu từ bội số của 4), tất cả các bit trừ giá trị thấp nhất đều giống nhau, vì vậy chúng xen kẽ giữa việc hủy lẫn nhau hoặc có giá trị ban đầu. Đúng là các bit thấp nhất chu kỳ 2 lần, nhưng 0 ^ 1 == 1 (tức là chúng không hủy). Lý do hai giá trị thấp nhất là đặc biệt là vì (00 ^ 01 ^ 10 ^ 11) == 00. Nói cách khác, cứ 4 giá trị mà bạn chuyển qua sẽ đưa bạn trở về 0 và do đó bạn có thể hủy bỏ tất cả các chu kỳ như vậy, đó là tại sao% 4 lại quan trọng.
FatalError

3
@Pandrei sự acó 2, không phải 0.
harold

1
Cột đó là xor đang chạy và 1 xor 2 là 3, vì vậy giá trị hiện tại trong hàng đó có vẻ chính xác với tôi.
FatalError,

58

Thêm vào câu trả lời tuyệt vời của FatalError, dòng return f(b)^f(a-1);này có thể được giải thích tốt hơn. Tóm lại, đó là vì XOR có những đặc tính tuyệt vời sau:

  • Nó có tính liên kết - Đặt dấu ngoặc ở bất cứ đâu bạn muốn
  • Nó có tính chất giao hoán - có nghĩa là bạn có thể di chuyển các toán tử xung quanh (chúng có thể "đi làm")

Đây là cả hai hoạt động:

(a ^ b ^ c) ^ (d ^ e ^ f) = (f ^ e) ^ (d ^ a ^ b) ^ c
  • tự đảo ngược

Như thế này:

a ^ b = c
c ^ a = b

Cộng và nhân là hai ví dụ về các toán tử kết hợp / giao hoán khác, nhưng chúng không tự đảo ngược. Ok, vậy, tại sao những thuộc tính này lại quan trọng? Chà, một lộ trình đơn giản là mở rộng nó thành những gì thực sự là, và sau đó bạn có thể thấy những thuộc tính này hoạt động.

Đầu tiên, hãy xác định những gì chúng ta muốn và gọi nó là n:

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

Nếu nó hữu ích, hãy nghĩ về XOR (^) như thể nó là một phép cộng.

Hãy cũng xác định hàm:

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

blớn hơn a, vì vậy, chỉ cần thêm vào một vài dấu ngoặc đơn một cách an toàn (mà chúng ta có thể làm được vì nó có tính liên kết), chúng ta cũng có thể nói điều này:

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

Đơn giản hóa thành:

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

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

Tiếp theo, chúng tôi sử dụng thuộc tính đảo ngược và tính phổ biến đó để cung cấp cho chúng tôi đường kỳ diệu:

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

Nếu bạn đang nghĩ về XOR giống như một phép cộng, bạn sẽ bỏ qua một số trừ ở đó. XOR là XOR những gì cộng là để trừ!

Làm cách nào để tôi tự nghĩ ra điều này?

Ghi nhớ các thuộc tính của toán tử logic. Làm việc với chúng gần giống như một phép cộng hoặc nhân nếu nó hữu ích. Có cảm giác không bình thường khi và (&), xor (^) và hoặc (|) là liên kết, nhưng chúng là vậy!

Trước tiên, hãy chạy quá trình triển khai ngây thơ, tìm kiếm các mẫu trong đầu ra, sau đó bắt đầu tìm các quy tắc xác nhận mẫu là đúng. Đơn giản hóa việc triển khai của bạn hơn nữa và lặp lại. Đây có lẽ là lộ trình mà người tạo ban đầu đã thực hiện, nổi bật bởi thực tế là nó không hoàn toàn tối ưu (tức là sử dụng câu lệnh switch thay vì một mảng).


3
Điều này làm tôi nhớ lại khóa học Toán rời rạc mà tôi đã học năm ngoái ở trường đại học. Những ngày vui vẻ. Điều xuất hiện trong đầu tôi ngay lập tức sau khi đọc bộ truyện tranh XKCD này .
Sean Francis N. Ballais

3

Tôi phát hiện ra rằng mã dưới đây cũng đang hoạt động giống như giải pháp được đưa ra trong câu hỏi.

Có thể điều này được tối ưu hóa một chút nhưng nó chỉ là những gì tôi nhận được từ việc quan sát sự lặp lại như được đưa ra trong câu trả lời được chấp nhận,

Tôi muốn biết / hiểu bằng chứng toán học đằng sau mã đã cho, như được giải thích trong câu trả lời của @Luke Briggs

Đây là mã 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

Tôi đã giải quyết vấn đề với đệ quy. Tôi chỉ đơn giản là chia tập dữ liệu thành một phần gần như bằng nhau cho mỗi lần lặp.

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

Hãy cho tôi biết suy nghĩ của bạn về giải pháp. Rất vui khi nhận được phản hồi về cải tiến. Giải pháp được đề xuất tính toán XOR theo độ phức tạp 0 (log N).

Cảm ơn bạn


Cái này có cùng độ phức tạp Tính toán với phép tính m ^ (m + 1) ^ ... ^ (n-1) ^ n thông thường. Đây là 0 (n).
Thế Anh Nguyễn

0

Để hỗ trợ XOR từ 0 đến N, mã được cung cấp cần được sửa đổi như bên dưới,

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);
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.