D - XOR World

はじめに

競技プログラミングAtCoder Beginner Contest 121
D - XOR World
言語はC#
何かあればTwitter→@pirorirori_n712まで

問題

問題文

f(A,B)をA,A+1,...,Bの排他的論理和としたとき、f(A,B)を求めてください。

▼排他的論理和とは
整数c1,c2,...,cnのビットごとの排他的論理和yは、以下のように定義されます。

・yを二進表記した際の2k(k≥0) の位の数は、
c1,c2,...,cnのうち、
二進表記した際の2kの位の数が1となるものが奇数個ならば1、偶数個ならば0である。

例えば、3と5の排他的論理和は6です(二進数表記すると: 011 と 101 の排他的論理和は 110 です)。

制約

・入力は全て整数である。
・0≤A≤B≤10^12

入力

入力は以下の形式で標準入力から与えられる。

A B

出力

f(A,B)を計算し、出力せよ。

解き方

以下を参考にしています。
drken1215.hatenablog.com
qiita.com

f(A,B)(A,A+1,...,B)の排他的論理和

まず、
求めなければならない「f(A,B)(A,A+1,...,Bの排他的論理和)」は
「f(0,B) XOR f(0,A-1)((0~Bの排他的論理和) XOR (0~(A-1)の排他的論理和))」と書き換えることができます。

これは、
XORの性質としてX XOR X=0(Xは任意の数)というものがあり
f(0,B)とf(0,A-1)のXORをとることによって
両者で重なっているf(0,A-1)の部分が全て0になり(X XOR X=0)
最終的に残る部分がf(A,B)になるからです。

参考と具体例:
qiita.com

また、通常の足し算ならば
3~10までの和を求める場合
上と同じ形にすると
(1~10までの和)ー(1~3までの和)=(3~10までの和)
というように
全体を足した数から不要な部分を引くことにより
欲しい部分が求まりますが
XORでは引くのではなく、
XORすることで答えが出すことができます。

「足し算」と「引き算」がどちらもXORであることも
XORの特徴らしいです。

参考と具体例:
drken1215.hatenablog.com

特徴を見つける

f(0,X)を書き出してみると以下のようになります。

X 2進数 f(0,X)
0 0 0
1 1 1
2 10 11
3 11 0
4 100 100
5 101 1
6 110 111
7 111 0
8 1000 1000
9 1001 1
10 1010 1011
11 1011 0
12 1100 1100
13 1101 1
14 1110 1111
15 1111 0

上の書き出しから
Xが奇数のとき、f(0,X)は0と1を繰り返す
ということが分かります。

以上から、
以下のようにすればf(0,X)を求められることが分かります。

①Xが奇数のとき
(X+1)/2が偶数なら0、奇数なら1
すなわち、(X+1)/2%2

②Xが偶数のとき
Xが奇数のときの値は
上の式にXを入れるだけですぐ出せるので、これを利用する。
(Xが偶数のとき
f(0,X-1)とf(0,X+1)の値は上の式に入れるだけで
答えが分かる。)

出し方は以下の二通り
①f(0,X-1)を使う
②f(0,X+1)を使う

①f(0,X-1)を使う
f(0,X-1)はまだXとXORしていない状態なので
f(0,X)を出すには「X XOR f(0,X-1)」をする。

X 2進数 f(0,X)
3 11 0
4 100 100

Xを4とすると、
f(0,X-1)はf(0,3)すなわち0
f(0,4)は 0 XOR 100=100

②f(0,X+1)を使う
f(0,X+1)は余分に X+1 が XOR されている状態なので
f(0,X)を出すには「(X+1) XOR f(0,X+1)」をする。

X 2進数 f(0,X)
4 100 100
5 101 1

Xを4とすると、
f(0,X+1)はf(0,5)すなわち1
f(0,4)は 1 XOR 101=100
※XORは引き算もXOR

下のコードでは①を用いています。

全体のコード

using System;
using System.Linq;
using System.Collections.Generic;

class Damondai
{
    static void Main()
    {
        var ab = Console.ReadLine().Split(' ').Select(x => Int64.Parse(x)).ToArray();
        var A = ab[0];
        var B = ab[1];
        //f(A,B)をf(0,B)^f(0,A-1)に変形
        var ans = Solve(A - 1) ^ Solve(B);
        Console.WriteLine(ans);
    }
    static long Solve(long num)
    {
        //奇数の場合
        if (num % 2 == 1) return Calc(num);
        //偶数の場合
        else
        {
            //0なら0を返す
            if (num <= 0) return 0;
            //Xが偶数のとき f(0,X)=X^(X-1)
            return num ^ Calc(num - 1);
        }
    }
    static long Calc(long num)
    {
        return (num + 1) / 2 % 2;
    }
}