Skip to content

nim_product.hpp

SECTIONMath INCLUDEnoya/math/nim_product.hpp

计算 64 位 nimber 的 nim 乘法;用于 Sprague–Grundy 值落在 nim 域乘法结构的博弈题。

\[ \displaystyle a \otimes b \]

Complexity: Time: \(O(1)\) per 64-bit product after \(O(2^{16})\) preprocessing. Space: \(O(2^{16})\) bytes (71 KiB).

AC 记录:nim_product_64。

跳到代码 · GitHub ↗

Implementation

当前头文件,省略 include guard;依赖见 #include。

/// @complexity Time: O(1) per 64-bit product after O(2^16) preprocessing.
/// Space: O(2^16) bytes (71 KiB).

#include <array>
#include <bit>
#include <cassert>
#include <cstdint>

namespace noya {

namespace nim_product_internal {

using u64 = std::uint64_t;

inline u64 recursive_product(u64 a, u64 b, int lg) {
  assert(lg >= 1 && lg <= 64 && (lg & (lg - 1)) == 0);
  if (a == 0 || b == 0) {
    return 0;
  }
  if (a == 1) {
    return b;
  }
  if (b == 1) {
    return a;
  }
  if (lg == 1) {
    return a & b;
  }

  int hal = lg / 2;
  u64 mas = (u64(1) << hal) - 1;
  u64 al = a & mas;
  u64 ah = a >> hal;
  u64 bl = b & mas;
  u64 bh = b >> hal;

  u64 ll = recursive_product(al, bl, hal);
  u64 hh = recursive_product(ah, bh, hal);
  u64 com = recursive_product(al ^ ah, bl ^ bh, hal);
  u64 red = recursive_product(hh, u64(1) << (hal - 1), hal);
  return (ll ^ red) ^ ((com ^ ll) << hal);
}

struct tables {
  std::array<std::uint8_t, 65536> mul{};
  // Blocks 0, 1..2, 3..6 reduce products in the 8-, 16-, and 32-bit fields.
  std::array<std::array<std::uint32_t, 256>, 7> red{};

  tables() {
    for (unsigned a = 1; a < 256; ++a) {
      for (unsigned b = 1; b < 256; ++b) {
        unsigned p = b & (b - 1), q = a & (a - 1);
        auto &v = mul[(a << 8) | b];
        if (p) v = mul[(a << 8) | p] ^ mul[(a << 8) | (b ^ p)];
        else if (q) v = mul[(q << 8) | b] ^ mul[((a ^ q) << 8) | b];
        else v = std::uint8_t(recursive_product(a, b, 8));
      }
    }
    for (int w = 8; w <= 32; w *= 2) {
      for (int i = 0; i < w / 8; ++i) {
        std::array<std::uint32_t, 8> bas{};
        for (int j = 0; j < 8; ++j) {
          bas[j] = std::uint32_t(recursive_product(
              u64(1) << (i * 8 + j), u64(1) << (w - 1), w));
        }
        auto &t = red[w / 8 - 1 + i];
        for (unsigned m = 1; m < 256; ++m)
          t[m] = t[m & (m - 1)] ^ bas[std::countr_zero(m)];
      }
    }
  }

  template<int w> u64 product(u64 a, u64 b) const {
    if constexpr (w == 8) {
      return mul[(a << 8) | b];
    } else {
      constexpr int h = w / 2;
      constexpr u64 mask = (u64(1) << h) - 1;
      u64 al = a & mask, ah = a >> h, bl = b & mask, bh = b >> h;
      u64 lo = product<h>(al, bl), hi = product<h>(ah, bh);
      u64 mid = product<h>(al ^ ah, bl ^ bh), r = 0;
      // Multiply hi by 2^(h-1), a linear map over the binary field.
      for (int i = 0; i < h / 8; ++i)
        r ^= red[h / 8 - 1 + i][(hi >> (i * 8)) & 255];
      return lo ^ r ^ ((mid ^ lo) << h);
    }
  }
};

} // namespace nim_product_internal

/// @brief Compute the 64-bit nim product. For alpha=2^h, the quadratic
/// extension satisfies alpha^2=alpha xor 2^(h-1). Three half-width products
/// per level reduce a query to 27 byte products. Small linear tables handle
/// multiplication by 2^(h-1); all tables are initialized on the first use.
inline std::uint64_t nim_product(std::uint64_t a, std::uint64_t b) {
  if (a == 0 || b == 0) return 0;
  static const nim_product_internal::tables tbl;
  return tbl.product<64>(a, b);
}

} // namespace noya

 // NOYA_NIM_PRODUCT_HPP
#ifndef NOYA_NIM_PRODUCT_HPP
#define NOYA_NIM_PRODUCT_HPP 1

/// @complexity Time: O(1) per 64-bit product after O(2^16) preprocessing.
/// Space: O(2^16) bytes (71 KiB).

#include <array>
#include <bit>
#include <cassert>
#include <cstdint>

namespace noya {

namespace nim_product_internal {

using u64 = std::uint64_t;

inline u64 recursive_product(u64 a, u64 b, int lg) {
  assert(lg >= 1 && lg <= 64 && (lg & (lg - 1)) == 0);
  if (a == 0 || b == 0) {
    return 0;
  }
  if (a == 1) {
    return b;
  }
  if (b == 1) {
    return a;
  }
  if (lg == 1) {
    return a & b;
  }

  int hal = lg / 2;
  u64 mas = (u64(1) << hal) - 1;
  u64 al = a & mas;
  u64 ah = a >> hal;
  u64 bl = b & mas;
  u64 bh = b >> hal;

  u64 ll = recursive_product(al, bl, hal);
  u64 hh = recursive_product(ah, bh, hal);
  u64 com = recursive_product(al ^ ah, bl ^ bh, hal);
  u64 red = recursive_product(hh, u64(1) << (hal - 1), hal);
  return (ll ^ red) ^ ((com ^ ll) << hal);
}

struct tables {
  std::array<std::uint8_t, 65536> mul{};
  // Blocks 0, 1..2, 3..6 reduce products in the 8-, 16-, and 32-bit fields.
  std::array<std::array<std::uint32_t, 256>, 7> red{};

  tables() {
    for (unsigned a = 1; a < 256; ++a) {
      for (unsigned b = 1; b < 256; ++b) {
        unsigned p = b & (b - 1), q = a & (a - 1);
        auto &v = mul[(a << 8) | b];
        if (p) v = mul[(a << 8) | p] ^ mul[(a << 8) | (b ^ p)];
        else if (q) v = mul[(q << 8) | b] ^ mul[((a ^ q) << 8) | b];
        else v = std::uint8_t(recursive_product(a, b, 8));
      }
    }
    for (int w = 8; w <= 32; w *= 2) {
      for (int i = 0; i < w / 8; ++i) {
        std::array<std::uint32_t, 8> bas{};
        for (int j = 0; j < 8; ++j) {
          bas[j] = std::uint32_t(recursive_product(
              u64(1) << (i * 8 + j), u64(1) << (w - 1), w));
        }
        auto &t = red[w / 8 - 1 + i];
        for (unsigned m = 1; m < 256; ++m)
          t[m] = t[m & (m - 1)] ^ bas[std::countr_zero(m)];
      }
    }
  }

  template<int w> u64 product(u64 a, u64 b) const {
    if constexpr (w == 8) {
      return mul[(a << 8) | b];
    } else {
      constexpr int h = w / 2;
      constexpr u64 mask = (u64(1) << h) - 1;
      u64 al = a & mask, ah = a >> h, bl = b & mask, bh = b >> h;
      u64 lo = product<h>(al, bl), hi = product<h>(ah, bh);
      u64 mid = product<h>(al ^ ah, bl ^ bh), r = 0;
      // Multiply hi by 2^(h-1), a linear map over the binary field.
      for (int i = 0; i < h / 8; ++i)
        r ^= red[h / 8 - 1 + i][(hi >> (i * 8)) & 255];
      return lo ^ r ^ ((mid ^ lo) << h);
    }
  }
};

} // namespace nim_product_internal

/// @brief Compute the 64-bit nim product. For alpha=2^h, the quadratic
/// extension satisfies alpha^2=alpha xor 2^(h-1). Three half-width products
/// per level reduce a query to 27 byte products. Small linear tables handle
/// multiplication by 2^(h-1); all tables are initialized on the first use.
inline std::uint64_t nim_product(std::uint64_t a, std::uint64_t b) {
  if (a == 0 || b == 0) return 0;
  static const nim_product_internal::tables tbl;
  return tbl.product<64>(a, b);
}

} // namespace noya

#endif // NOYA_NIM_PRODUCT_HPP
/// @complexity Time: O(1) per 64-bit product after O(2^16) preprocessing.
/// Space: O(2^16) bytes (71 KiB).

#include <array>
#include <bit>
#include <cassert>
#include <cstdint>

namespace noya {

namespace nim_product_internal {

using u64 = std::uint64_t;

inline u64 recursive_product(u64 a, u64 b, int lg) {
  assert(lg >= 1 && lg <= 64 && (lg & (lg - 1)) == 0);
  if (a == 0 || b == 0) {
    return 0;
  }
  if (a == 1) {
    return b;
  }
  if (b == 1) {
    return a;
  }
  if (lg == 1) {
    return a & b;
  }

  int hal = lg / 2;
  u64 mas = (u64(1) << hal) - 1;
  u64 al = a & mas;
  u64 ah = a >> hal;
  u64 bl = b & mas;
  u64 bh = b >> hal;

  u64 ll = recursive_product(al, bl, hal);
  u64 hh = recursive_product(ah, bh, hal);
  u64 com = recursive_product(al ^ ah, bl ^ bh, hal);
  u64 red = recursive_product(hh, u64(1) << (hal - 1), hal);
  return (ll ^ red) ^ ((com ^ ll) << hal);
}

struct tables {
  std::array<std::uint8_t, 65536> mul{};
  // Blocks 0, 1..2, 3..6 reduce products in the 8-, 16-, and 32-bit fields.
  std::array<std::array<std::uint32_t, 256>, 7> red{};

  tables() {
    for (unsigned a = 1; a < 256; ++a) {
      for (unsigned b = 1; b < 256; ++b) {
        unsigned p = b & (b - 1), q = a & (a - 1);
        auto &v = mul[(a << 8) | b];
        if (p) v = mul[(a << 8) | p] ^ mul[(a << 8) | (b ^ p)];
        else if (q) v = mul[(q << 8) | b] ^ mul[((a ^ q) << 8) | b];
        else v = std::uint8_t(recursive_product(a, b, 8));
      }
    }
    for (int w = 8; w <= 32; w *= 2) {
      for (int i = 0; i < w / 8; ++i) {
        std::array<std::uint32_t, 8> bas{};
        for (int j = 0; j < 8; ++j) {
          bas[j] = std::uint32_t(recursive_product(
              u64(1) << (i * 8 + j), u64(1) << (w - 1), w));
        }
        auto &t = red[w / 8 - 1 + i];
        for (unsigned m = 1; m < 256; ++m)
          t[m] = t[m & (m - 1)] ^ bas[std::countr_zero(m)];
      }
    }
  }

  template<int w> u64 product(u64 a, u64 b) const {
    if constexpr (w == 8) {
      return mul[(a << 8) | b];
    } else {
      constexpr int h = w / 2;
      constexpr u64 mask = (u64(1) << h) - 1;
      u64 al = a & mask, ah = a >> h, bl = b & mask, bh = b >> h;
      u64 lo = product<h>(al, bl), hi = product<h>(ah, bh);
      u64 mid = product<h>(al ^ ah, bl ^ bh), r = 0;
      // Multiply hi by 2^(h-1), a linear map over the binary field.
      for (int i = 0; i < h / 8; ++i)
        r ^= red[h / 8 - 1 + i][(hi >> (i * 8)) & 255];
      return lo ^ r ^ ((mid ^ lo) << h);
    }
  }
};

} // namespace nim_product_internal

/// @brief Compute the 64-bit nim product. For alpha=2^h, the quadratic
/// extension satisfies alpha^2=alpha xor 2^(h-1). Three half-width products
/// per level reduce a query to 27 byte products. Small linear tables handle
/// multiplication by 2^(h-1); all tables are initialized on the first use.
inline std::uint64_t nim_product(std::uint64_t a, std::uint64_t b) {
  if (a == 0 || b == 0) return 0;
  static const nim_product_internal::tables tbl;
  return tbl.product<64>(a, b);
}

} // namespace noya

 // NOYA_NIM_PRODUCT_HPP