use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::OnceLock;
#[inline]
pub fn nim_add(a: u128, b: u128) -> u128 {
a ^ b
}
thread_local! {
static POW2_MEMO: RefCell<HashMap<(usize, usize), u128>> = RefCell::new(HashMap::new());
}
fn nim_mul_pow2(i: usize, j: usize) -> u128 {
let key = if i <= j { (i, j) } else { (j, i) };
if let Some(v) = POW2_MEMO.with(|m| m.borrow().get(&key).copied()) {
return v;
}
let single = i ^ j;
let common = i & j;
let clean: u128 = 1u128 << single;
let result = if common == 0 {
clean
} else {
let mut squared: u128 = 1; let mut c = common;
while c != 0 {
let n = c.trailing_zeros() as usize;
c &= c - 1;
let f = 1u128 << (1u128 << n); let factor = f ^ (f >> 1); squared = nim_mul(squared, factor);
}
nim_mul(clean, squared)
};
POW2_MEMO.with(|m| m.borrow_mut().insert(key, result));
result
}
pub fn nim_mul(a: u128, b: u128) -> u128 {
let mut acc = 0u128;
let mut aa = a;
while aa != 0 {
let i = aa.trailing_zeros() as usize;
aa &= aa - 1;
let mut bb = b;
while bb != 0 {
let j = bb.trailing_zeros() as usize;
bb &= bb - 1;
acc ^= nim_mul_pow2(i, j);
}
}
acc
}
#[inline]
fn apply_f2_linear_map(mut x: u128, table: &[u128; 128]) -> u128 {
let mut acc = 0u128;
while x != 0 {
let i = x.trailing_zeros() as usize;
x &= x - 1;
acc ^= table[i];
}
acc
}
fn square_basis() -> &'static [u128; 128] {
static SQUARE_BASIS: OnceLock<[u128; 128]> = OnceLock::new();
SQUARE_BASIS.get_or_init(|| {
let mut basis = [0u128; 128];
for (i, slot) in basis.iter_mut().enumerate() {
*slot = nim_mul_pow2(i, i);
}
basis
})
}
fn invert_linear_basis(columns: &[u128; 128]) -> [u128; 128] {
let mut left = [0u128; 128];
let mut right = [0u128; 128];
for (col, &image) in columns.iter().enumerate() {
let mut bits = image;
while bits != 0 {
let row = bits.trailing_zeros() as usize;
bits &= bits - 1;
left[row] |= 1u128 << col;
}
right[col] = 1u128 << col;
}
for col in 0..128 {
let bit = 1u128 << col;
let pivot = (col..128)
.find(|&row| left[row] & bit != 0)
.expect("Frobenius basis matrix must be invertible");
left.swap(col, pivot);
right.swap(col, pivot);
for row in 0..128 {
if row != col && left[row] & bit != 0 {
left[row] ^= left[col];
right[row] ^= right[col];
}
}
}
let mut inverse_columns = [0u128; 128];
for (row, &row_bits) in right.iter().enumerate() {
let mut bits = row_bits;
while bits != 0 {
let col = bits.trailing_zeros() as usize;
bits &= bits - 1;
inverse_columns[col] |= 1u128 << row;
}
}
inverse_columns
}
fn sqrt_basis() -> &'static [u128; 128] {
static SQRT_BASIS: OnceLock<[u128; 128]> = OnceLock::new();
SQRT_BASIS.get_or_init(|| invert_linear_basis(square_basis()))
}
pub fn nim_pow(mut base: u128, mut exp: u128) -> u128 {
let mut acc = 1u128; while exp > 0 {
if exp & 1 == 1 {
acc = nim_mul(acc, base);
}
base = nim_square(base);
exp >>= 1;
}
acc
}
#[inline]
pub fn nim_square(x: u128) -> u128 {
apply_f2_linear_map(x, square_basis())
}
pub fn nim_frobenius_iter(mut x: u128, k: usize) -> u128 {
for _ in 0..k {
x = nim_square(x);
}
x
}
pub fn nim_sqrt(x: u128) -> u128 {
apply_f2_linear_map(x, sqrt_basis())
}
fn nim_pow_2k_minus_one(x: u128, k: usize) -> u128 {
match k {
0 => 1,
1 => x,
_ if k.is_multiple_of(2) => {
let y = nim_pow_2k_minus_one(x, k / 2);
nim_mul(nim_frobenius_iter(y, k / 2), y)
}
_ => {
let y = nim_pow_2k_minus_one(x, k - 1);
nim_mul(nim_square(y), x)
}
}
}
pub fn nim_inv(x: u128) -> Option<u128> {
if x == 0 {
None
} else {
Some(nim_square(nim_pow_2k_minus_one(x, 127)))
}
}