指定された範囲内のすべての数値のXORを見つける


99

広い範囲[a、b]が与えられます。「a」と「b」は通常、1〜4,000,000,000の範囲です。与えられた範囲内のすべての数値のXORを見つける必要があります。

この問題は、TopCoder SRMで使用されていました。試合で提出された解決策の1つを確認しましたが、その仕組みを理解できません。

誰かが勝利の解決策を説明するのを手伝ってくれませんか:

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()」はヘルパー関数です。


あなたの質問を少し編集しました。一部のコードの理由を説明してもかまいませんが、これを解決する他の方法の新しいリストは必要ありません。それはTopCoderにおまかせください。
Kev

@Kev問題ありません!私が書いたのは、すでに書かれたものを説明するのではなく、自分のやり方を好む人もいるからです。そして、どんな新しいアイデアも無駄になりません...;)
rajneesh2k10

これにはa<=0、またはの動作が未定義ですb<0long longは符号付きの型なのでx%4、負の入力の場合は負(または0)です。たぶんunsigned long long、そして/またはa & 3配列にインデックスを付けたいですか?
Peter Cordes

回答:


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]

最初の列はバイナリ表現で、次に10進数の結果とXORリストへのインデックス(a)との関係です。これは、すべての上位ビットがキャンセルされ、最下位の2ビットが4ごとに循環するために発生します。つまり、このようにして小さなルックアップテーブルに到達します。

ここで、[a、b]の一般的な範囲について検討します。f()[0、a-1]と[0、b]のXORを見つけるために使用できます。それ自体とXOR f(a-1)された値はすべてゼロであるため、はXORラン未満のすべての値をキャンセルしa、範囲[a、b]のXORを残します。


最小範囲のしきい値は0ではなく1です
Pencho Ilchev

2
@PenchoIlchev 0が含まれているかどうかは、モットの一種です-(n ^ 0)== n
FatalError

2
@ rajneesh2k10まあ、4の実行(4の倍数で開始)では、最下位ビットを除くすべてのビットが同じであるため、相互にキャンセルするか、元の値を持つかが交互に変わります。最下位ビットは2ごとに循環しますが、0 ^ 1 == 1です(つまり、キャンセルしません)。一番下の2つが特別な理由は、(00 ^ 01 ^ 10 ^ 11)== 00です。つまり、循環する4つの値ごとに0に戻り、そのような循環をすべてキャンセルできます。なぜa%4が重要なのか。
FatalError

3
@Pandrei a、2ない0あり
ハロルド

1
その列は実行中のxorであり、1 xor 2は3なので、その行の現在の値は私には正しいように見えます。
FatalError 2016

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

加算と乗算は、他の連想/可換演算子の2つの例ですが、逆になりません。では、なぜこれらのプロパティが重要なのでしょうか。まあ、単純なルートは、それを実際の状態に拡張することです。そうすれば、これらのプロパティが機能しているのがわかります。

まず、必要なものを定義して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ステートメントを使用する)によって強調されています。


3
これは、昨年大学で行った離散数学のコースを思い出させます。楽しい日々。これを読んだ直後に思い浮かんだのがこのXKCDコミックです。
Sean Francis N. Ballais 2017年

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)です。
THEアングエン

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);
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.