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


99

您将获得较大的范围[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]中所有数字的异或,而“ f()”是一个辅助函数。


我只是编辑了您的问题。我们不介意解释某些代码的原因,但是我们不需要新的其他方法列表即可解决此问题。留给TopCoder。
2012年

@Kev没有问题!我写这篇文章是因为有些人喜欢给出自己的方式,而不是解释已经写的东西。而且任何新想法永远都不会浪费...;)
rajneesh2k10 2012年

具有a<=0或的未定义行为b<0long long是带符号的类型,因此x%4对于负输入,它是负数(或0)。也许您想要unsigned long long和/或a & 3为数组建立索引?
彼得·科德斯

Answers:


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]

其中第一列是二进制表示形式,然后是十进制结果及其与XOR列表中的索引(a)的关系。发生这种情况是因为所有高位都被抵消,而最低位每4循环一次。因此,这就是到达该小的查询表的方法。

现在,考虑[a,b]的一般范围。我们可以f()用来查找[0,a-1]和[0,b]的XOR。由于任何与其自身f(a-1)进行XOR运算的值均为零,因此Just会抵消XOR运算中所有小于的值a,从而使您具有[a,b]范围的XOR。


最小范围阈值为1,而不是0
Pencho Ilchev 2012年

2
@PenchoIlchev它是否包含0是一种有争议的话题-(n ^ 0)== n
FatalError 2012年

2
@ rajneesh2k10好吧,在运行4(从4的倍数开始)中,除最低位外的所有位都相同,因此它们在互相抵消或具有原始值之间交替。的确,最低位每2个循环一次,但0 ^ 1 == 1(即它们不会取消)。最低的两个之所以特别是因为(00 ^ 01 ^ 10 ^ 11)==00。换句话说,您循环遍历的每4个值会使您回到0,因此您可以取消所有这些周期,即为什么a%4很重要。
FatalError 2012年

3
@Pandrei a那里是2,而不是0。–
harold

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

接下来,我们使用该反转属性和可交换性给我们神奇的一线:

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

如果您一直将XOR视为加法,则应该在其中减去减法。XOR是XOR的加法是减去!

我本人该怎么想?

记住逻辑运算符的属性。如果有帮助,几乎可以像加法或乘法那样与他们合作。和(&),异或(^)和或(|)是关联的,但它们确实是不寻常的!

首先运行天真的实现,在输出中查找模式,然后开始查找确认模式正确的规则。进一步简化您的实施并重复执行。这可能是原始创建者所采用的路线,但事实并非完全如此(即使用switch语句而不是数组),这一点突出了这一点。


3
这使我想起了我去年在大学修过的离散数学课程。好玩的日子。读完这篇文章后,我立刻想到的是这本XKCD漫画
肖恩·弗朗西斯·N·巴拉伊

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)。
对映阮

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);
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.