【问题标题】:Find XOR of all numbers in a given range查找给定范围内所有数字的异或
【发布时间】:2012-05-20 02:40:00
【问题描述】:

给您一个很大的范围 [a,b],其中“a”和“b”通常可以在 1 到 4,000,000,000 之间。你必须找出给定范围内所有数字的异或。

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。
  • @Kev 没问题!我这么写是因为有些人喜欢给出自己的方式,而不是解释已经写好的东西。任何新想法都不会浪费...... ;)
  • 这对于a<=0b<0 具有未定义的行为。 long long 是有符号类型,因此 x%4 对于负输入为负(或 0)。也许您希望 unsigned long long 和/或 a & 3 为数组编制索引?

标签: algorithm


【解决方案1】:

这是一个非常聪明的解决方案——它利用了在运行的 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 中小于a 的所有值,从而为您留下范围 [a,b] 的异或。

【讨论】:

  • 最小范围阈值为 1,而不是 0
  • @PenchoIlchev 无论是否包含 0 都没有实际意义 -- (n^0)==n
  • @rajneesh2k10 好吧,在 4 次运行中(从 4 的倍数开始),除最低位之外的所有位都相同,因此它们在相互抵消或具有其原始值之间交替。确实,最低位每 2 次循环一次,但 0^1 == 1(即它们不会取消)。最低的两个之所以特殊,是因为 (00 ^ 01 ^ 10 ^ 11) == 00。换句话说,每循环 4 个值就会使您回到 0,因此您可以取消所有此类循环,即为什么 a%4 很重要。
  • @Pandrei a 有 2 个,而不是 0 个。
  • 该列是正在运行的异或,1 异或 2 是 3,所以该行中的当前值对我来说看起来是正确的。
【解决方案2】:

除了 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 加是减!

我如何自己想出这个?

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

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

【讨论】:

  • 这让我想起了我去年在大学学习的离散数学课程。有趣的日子。读完这篇文章后我立即想到的是XKCD comic
  • It reverses itself 在技术上是“a 是自身的 inverse”,或者实际上我们可以将这个讨论一直延伸到基本群论...
【解决方案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];
}

【讨论】:

    【解决方案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。

    谢谢

    【讨论】:

    • 这个计算复杂度与正常的 m ^ (m+1) ^ ... ^ (n-1) ^ n 计算相同。这是 0(n)。
    【解决方案5】:

    为了支持0到N的异或,给定的代码需要修改如下,

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

    【讨论】:

      【解决方案6】:

      进一步补充 FatalError 的答案,可以证明(通过归纳)f() 中观察到的模式将每 4 个数字循环一次。

      我们试图证明对于每个整数k &gt;= 0

      f(4k + 1) = 1
      f(4k + 2) = 4k + 3
      f(4k + 3) = 0
      f(4k + 4) = 4k + 4
      

      其中f(n)1 ^ 2 ^ ... ^ n

      作为我们的基本情况,我们可以手动计算出

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

      对于我们的归纳步骤,假设这些方程在特定整数4x(即f(4x) = 4x)之前为真。我们想证明我们的方程对于4x + 14x + 24x + 34x + 4 是正确的。

      为了帮助编写和可视化证明,我们可以让b(x) 表示x 的二进制(base-2)字符串表示,例如
      b(7) = '111'b(9) = '1001'

      b(4x)     = 'b(x)00'
      b(4x + 1) = 'b(x)01'
      b(4x + 2) = 'b(x)10'
      b(4x + 3) = 'b(x)11'
      

      这是归纳步骤:

      Assume: f(4x) = 4x = 'b(x)00'
      Then:
      
      f(4x + 1) = f(4x)    ^ (4x + 1)  // by definition
                = f(4x)    ^ 'b(x)01'  // by definition
                = 'b(x)00' ^ 'b(x)01'  // from assumption
                = '01'                 // as b(x) ^ b(x) = 0
      
      f(4x + 2) = f(4x + 1) ^ (4x + 2)
                = f(4x + 1) ^ 'b(x)10'       
                = '01'      ^ 'b(x)10'
                = 'b(x)11'             // this is 4x + 3
      
      f(4x + 3) = f(4x + 2) ^ (4x + 3)
                = f(4x + 2) ^ 'b(x)11' 
                = 'b(x)11'  ^ 'b(x)11'
                = '00'
      
      For the last case, we don't use binary strings, 
      since we don't know what b(4x + 4) is.
      
      f(4x + 4) = f(4x + 3) ^ (4x + 4)
                = 0         ^ (4x + 4)
                = 4x + 4
      

      因此,该模式适用于4x 之后的接下来的四个数字,从而完成了证明。

      【讨论】:

        猜你喜欢
        • 2015-07-28
        • 1970-01-01
        • 2017-03-23
        • 2014-04-27
        • 1970-01-01
        • 2020-10-03
        • 2020-02-29
        • 1970-01-01
        相关资源
        最近更新 更多