주어진 범위에서 모든 숫자의 XOR 찾기


99

'a'와 'b'는 일반적으로 1에서 4,000,000,000 사이가 될 수있는 큰 범위 [a, b]가 제공됩니다. 주어진 범위에있는 모든 숫자의 XOR을 찾아야합니다.

이 문제는 TopCoder SRM에서 사용되었습니다. 경기에서 제출 된 솔루션 중 하나를 보았지만 어떻게 작동하는지 알 수 없습니다.

누군가가 성공적인 솔루션을 설명하는 데 도움을 줄 수 있습니까?

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

다음 getXor()은 전달 된 범위 [a, b]의 모든 숫자의 xor를 계산하는 실제 함수이며 "f ()"는 도우미 함수입니다.


나는 당신의 질문을 약간 수정했습니다. 일부 코드의 이유를 설명하는 데는 신경 쓰지 않지만이를 해결하기위한 다른 방법의 새로운 목록은 필요하지 않습니다. TopCoder에 맡기십시오.
Kev

@Kev 문제 없습니다! 어떤 사람들은 이미 쓰여진 것을 설명하는 것보다 자신의 방식을 선호하기 때문에 그것을 썼습니다. 그리고 새로운 아이디어는 결코 낭비가 아닙니다 ...;)
rajneesh2k10

a<=0또는에 대해 정의되지 않은 동작이 있습니다 b<0. long long부호있는 유형이므로 x%4음수 입력의 경우 음수 (또는 0)입니다 . 아마도 당신이 원 unsigned long long하거나 a & 3배열을 색인화하고 싶습니까?
Peter Cordes 2019 년

답변:


152

이것은 매우 영리한 솔루션입니다. 실행중인 XOR에 결과 패턴이 있다는 사실을 이용합니다. 이 f()함수는 [0, a]에서 실행 된 XOR 총계를 계산합니다. 이 표에서 4 비트 숫자를 살펴보십시오.

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]

첫 번째 열은 이진 표현이고 10 진수 결과 및 XOR 목록에 대한 인덱스 (a)와의 관계입니다. 이것은 모든 상위 비트가 취소되고 하위 2 비트가 매 4 주기로 발생하기 때문에 발생합니다. 따라서이 작은 조회 테이블에 도달하는 방법입니다.

이제 [a, b]의 일반적인 범위를 고려하십시오. f()[0, a-1] 및 [0, b]에 대한 XOR을 찾는 데 사용할 수 있습니다 . 자체적으로 XOR 처리 한 값은 0이므로는 f(a-1)XOR 실행의 모든 ​​값을. 미만으로 취소하여 a[a, b] 범위의 XOR을 남깁니다.


최소 범위 임계 값은 0이 아니라 1입니다.
Pencho Ilchev

2
@PenchoIlchev 0을 포함하는지 여부는 일종의 문제입니다-(n ^ 0) == n
FatalError

2
@ rajneesh2k10 글쎄, 4의 배수에서 시작하여 4의 배수에서 시작하면 가장 낮은 비트를 제외한 모든 비트가 동일하므로 서로를 취소하거나 원래 값을 갖는 것을 번갈아 가며 사용합니다. 가장 낮은 비트는 2 회마다 순환하지만 0 ^ 1 == 1 (즉, 취소하지 않음)은 사실입니다. 가장 낮은 2 개가 특별한 이유는 (00 ^ 01 ^ 10 ^ 11) == 00이기 때문입니다. 즉, 순환하는 4 개의 값마다 0으로 돌아가므로 이러한 모든 순환을 취소 할 수 있습니다. a % 4가 중요한 이유.
FatalError

3
@Pandrei a, 2 0이 아닌이
해롤드

1
해당 열은 실행중인 xor이고 1 xor 2는 3이므로 해당 행의 현재 값이 나에게 맞아 보입니다.
FatalError

58

FatalError의 훌륭한 답변에 추가하면 라인을 return f(b)^f(a-1);더 잘 설명 할 수 있습니다. 간단히 말해 XOR에는 다음과 같은 훌륭한 속성이 있기 때문입니다.

  • 그것은의 연관 - 장소 브래킷 어디든지 당신이 원하는
  • 그건 교환 법칙이 성립 - 수단은 당신이 주변에 연산자를 이동할 수 있습니다 (그들이 할 수있는 "통근")

두 가지 모두 작동합니다.

(a ^ b ^ c) ^ (d ^ e ^ f) = (f ^ e) ^ (d ^ a ^ b) ^ c
  • 그것은 그 자체를 반전

이렇게 :

a ^ b = c
c ^ a = b

더하기와 곱하기는 다른 연관 / 교환 연산자의 두 가지 예이지만, 그 자체를 뒤집지는 않습니다. 좋습니다. 이러한 속성이 왜 중요한가요? 글쎄요, 간단한 방법은 그것을 실제 상태로 확장하는 것입니다. 그러면 여러분은 이러한 속성을 직장에서 볼 수 있습니다.

먼저 우리가 원하는 것을 정의하고 그것을 n이라고 부릅시다 :

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

도움이된다면 XOR (^)을 추가 한 것처럼 생각하십시오.

함수를 정의 해 보겠습니다.

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

b는보다 큽니다 a. 따라서 몇 개의 추가 괄호 (연관 적이므로 가능함)를 안전하게 넣어서 다음과 같이 말할 수도 있습니다.

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

다음을 단순화합니다.

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

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

다음으로, 우리는 그 반전 속성과 commutivity를 사용하여 우리에게 마법의 선을줍니다.

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

XOR을 더하기처럼 생각했다면 거기에 빼기를 떨어 뜨렸을 것입니다. XOR은 XOR에 추가하는 것입니다.

이걸 어떻게 생각해?

논리 연산자의 속성을 기억하십시오. 도움이된다면 더하기 또는 곱하기와 같이 작업하십시오. and (&), xor (^) 및 or (|)가 연관성이 있다는 것은 이상하게 느껴지지만 그렇습니다!

먼저 순진한 구현을 실행하고 출력에서 ​​패턴을 찾은 다음 패턴이 참인지 확인하는 규칙을 찾기 시작합니다. 구현을 더욱 단순화하고 반복하십시오. 이것은 아마도 완전히 최적이 아니라는 사실에 의해 강조된 원래 제작자가 취한 경로 일 것입니다 (예 : 배열보다는 switch 문 사용).


3
이것은 제가 작년에 대학에서 수강했던 이산 수학 과정을 생각 나게합니다. 즐거운 날. 이 책을 읽은 후 바로 떠오른 것은 바로이 XKCD 만화 입니다.
Sean Francis N. Ballais

3

아래 코드도 질문에 주어진 솔루션처럼 작동한다는 것을 알았습니다.

이것은 거의 최적화되지 않았지만 받아 들여진 대답에 주어진 반복을 관찰하여 얻은 것입니다.

@Luke Briggs의 답변에서 설명한 것처럼 주어진 코드의 수학적 증거를 알고 싶습니다.

여기 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

재귀 문제를 해결했습니다. 모든 반복에 대해 데이터 세트를 거의 동일한 부분으로 나누기 만하면됩니다.

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

솔루션에 대한 귀하의 생각을 알려주십시오. 개선 피드백을 받게되어 기쁩니다. 제안 된 솔루션은 0 (log N) 복잡도로 XOR을 계산합니다.

감사합니다


이것은 일반적인 m ^ (m + 1) ^ ... ^ (n-1) ^ n 계산과 동일한 계산 복잡도를 갖습니다. 이것은 0 (n)입니다.
Thế Anh Nguyễn

0

0에서 N까지의 XOR을 지원하려면 주어진 코드를 아래와 같이 수정해야합니다.

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);
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.