查找给定范围内所有数字的XOR

时间:2012-05-20 02:40:01

标签: algorithm

您获得了大范围[a,b],其中'a'和'b'通常在1到4,000,000,000之间。你必须找出给定范围内所有数字的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()”是辅助函数。

5 个答案:

答案 0 :(得分:135)

这是一个非常聪明的解决方案 - 它利用了正在运行的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]

其中第一列是二进制表示,然后是十进制结果及其与索引(a)的关系到XOR列表中。发生这种情况是因为所有高位都取消,最低的两位每4位循环。所以,这就是如何到达那个小查找表。

现在,考虑[a,b]的一般范围。我们可以使用f()来找到[0,a-1]和[0,b]的XOR。由于与自身的异或值为零,f(a-1)只是取消XOR运行中的所有值小于a,从而使您得到范围[a,b]的XOR。 / p>

答案 1 :(得分:46)

添加FatalError的答案很好,可以更好地解释第return f(b)^f(a-1);行。简而言之,因为XOR具有以下优良特性:

  • 关联 - 在任意位置放置括号
  • 可交换 - 这意味着您可以移动运营商(他们可以&#34;通勤&#34;)

以下两者都在行动中:

(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

接下来,我们使用反转属性和交流来为我们提供神奇的线​​条:

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

如果您一直认为XOR就像添加一样,那么你会在那里减去。 XOR是XOR要减去的是什么!

我如何自己想出这个?

记住逻辑运算符的属性。如果它有帮助的话,几乎就像添加或乘法一样使用它们。感觉不寻常和(&amp;),xor(^)和或(|)是关联的,但它们是!

首先运行朴素实现,在输出中查找模式,然后开始查找确认模式为真的规则。进一步简化您的实现并重复。这可能是原始创建者所采用的路径,并强调它并非完全最优(即使用switch语句而不是数组)。

答案 2 :(得分: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];
}

答案 3 :(得分:0)

我用递归解决了这个问题。我只是将数据集划分为每次迭代几乎相等的部分。

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。

谢谢

答案 4 :(得分:0)

要支持从0到N的XOR,需要对给出的代码进行如下修改,

/path/to/mynpmroot/then/very/deep/here