You are given a large range [a,b] where 'a' and 'b' can be typically between 1 and 4,000,000,000 inclusive. You have to find out the XOR of all the numbers in the given range.
This problem was used in TopCoder SRM. I saw one of the solutions submitted in the match and I'm not able to figure out how its working.
Could someone help explain the winning solution:
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);
}
Here, getXor()
is the actual function to calculate the xor of all number in the passed range [a,b] and "f()" is a helper function.
I have solved the problem with recursion. I simply divide the dataset into an almost equal part for every iteration.
Let me know your thoughts over the solution. Happy to get improvement feedbacks. The proposed solution calculates the XOR in 0(log N) complexity.
Thank you
This is a pretty clever solution -- it exploits the fact that there is a pattern of results in the running XORs. The
f()
function calculates the XOR total run from [0, a]. Take a look at this table for 4-bit numbers:Where the first column is the binary representation and then the decimal result and its relation to its index (a) into the XOR list. This happens because all the upper bits cancel and the lowest two bits cycle every 4. So, that's how to arrive at that little lookup table.
Now, consider for a general range of [a,b]. We can use
f()
to find the XOR for [0,a-1] and [0,b]. Since any value XOR'd with itself is zero, thef(a-1)
just cancels out all the values in the XOR run less thana
, leaving you with the XOR of the range [a,b].To support XOR from 0 to N the code given needed to be modified as below,
Adding to FatalError's great answer, the line
return f(b)^f(a-1);
could be explained better. In short, it's because XOR has these wonderful properties:Here's both in action:
Like this:
Add and multiply are two examples of other associative/ commutative operators, but they don't reverse themselves. Ok, so, why are these properties important? Well, a simple route is to expand it out into what it really is, and then you can see these properties at work.
First, let's define what we want and call it n:
If it helps, think of XOR (^) as if it was an add.
Let's also define the function:
b
is greater thana
, so just by safely dropping in a few extra brackets (which we can because it's associative), we can also say this:Which simplifies to:
Next, we use that reversal property and commutivity to give us the magic line:
If you've been thinking of XOR like an add, you would've dropped in a subtract there. XOR is to XOR what add is to subtract!
How do I come up with this myself?
Remember the properties of logical operators. Work with them almost like an add or multiply if it helps. It feels unusual that and (&), xor (^) and or (|) are associative, but they are!
Run the naive implementation through first, look for patterns in the output, then start finding rules which confirm the pattern is true. Simplify your implementation even further and repeat. This is probably the route that the original creator took, highlighted by the fact that it's not completely optimal (i.e. use a switch statement rather than an array).
I found out that the below code is also working like the solution given in the question.
May be this is little optimized but its just what I got from observing repetition like given in the accepted answer,
I would like to know / understand the mathematical proof behind the given code, like explained in the answer by @Luke Briggs
Here is that JAVA code