‘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 ()”는 도우미 함수입니다.
답변
이것은 매우 영리한 솔루션입니다. 실행중인 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을 남깁니다.
답변
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 문 사용).
답변
아래 코드도 질문에 주어진 솔루션처럼 작동한다는 것을 알았습니다.
이것은 거의 최적화되지 않았지만 받아 들여진 대답에 주어진 반복을 관찰하여 얻은 것입니다.
@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];
}
답변
재귀 문제를 해결했습니다. 모든 반복에 대해 데이터 세트를 거의 동일한 부분으로 나누기 만하면됩니다.
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을 계산합니다.
감사합니다
답변
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);
}