SRM601D1Med WinterAndSnowmen
xorについての知見が増えた
問題概要(原文)
以下を満たす集合 $A$,$B$ がある. $x=A_1 \oplus A_2 \oplus \ldots, y=B_1 \oplus B_2 \oplus \ldots,$ としたとき $x<y$ となるような $A,B$ の選び方は何通りあるか.
- $A \subseteq \{1,2,\ldots, N \}$
- $B \subseteq \{1,2,\ldots, M \}$
- $A \cap B = \phi$
考察
xor を取ったときに $x<y$ であるということは $2$ 進数表記で先頭 $d$ 桁において, $x$ と $y$ の値が一致し,$d+1$ 桁目が $0,1$ となっているということである.
そのような $d$ を全探索する方針で考える. $x$ と $y$ の先頭 $d$ 桁が一致するとは, $x \oplus y$ の先頭 $d$ 桁が全て $0$ ということと同値.
このようにして数えると, $O(max(N,M)^2log(max(N,M)))$ となる.
ソースコード
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Debug = System.Diagnostics.Debug;
using StringBuilder = System.Text.StringBuilder;
public class WinterAndSnowmen
{
public int getNumber(int N, int M)
{
const int MOD = 1000000007;
var max = Math.Max(N, M);
int ans = 0;
for (int k = 0; k <= 10; k++)
{
var dp = Enumerate(2048, x => new int[2, 2]);
dp[0][0, 0] = 1;
for (int i = 1; i <= max; i++)
{
var p = i >> k;
var q = p >> 1;
var r = p & 1;
var next = Enumerate(2048 >> k, x => new int[2, 2]);
for (int j = 0; j < 2048 >> k; j++)
for (int u = 0; u < 2; u++)
for (int v = 0; v < 2; v++)
{
if (dp[j][u, v] == 0) continue;
if (i <= N)
if ((next[j ^ q][u ^ r, v] += dp[j][u, v]) >= MOD) next[j ^ q][u ^ r, v] -= MOD;
if (i <= M)
if ((next[j ^ q][u, v ^ r] += dp[j][u, v]) >= MOD) next[j ^ q][u, v ^ r] -= MOD;
if ((next[j][u, v] += dp[j][u, v]) >= MOD) next[j][u, v] -= MOD;
}
dp = next;
}
ans += dp[0][0, 1];
ans %= MOD;
}
return ans;
}
static public T[] Enumerate<T>(int n, Func<int, T> f) { var a = new T[n]; for (int i = 0; i < n; ++i) a[i] = f(i); return a; }
}
コメント
- 先頭 $d$ 桁が一致するのを全探索するのは思いついたが,$x \oplus y$ を状態に持つのは思いつかなかった
- 定数倍がめちゃくちゃきつかった.なんでこんな重いんだろう.キャッシュヒット効率とかは効きそう.