cp-library

競技プログラミング用 C++ ライブラリ

View the Project on GitHub qandori/cp-library

:warning: AND-OR などの論理畳み込み

convolution/boolean_convolution.hpp · GitHub で見る

共通ページ

#include "lib/convolution/boolean_convolution.hpp"

std::vector<unsigned> a{1, 2, 3, 4}, b{5, 6, 7, 8};
auto c = and_or_convolution(a, b); // {7,6,7,0}

*_or_convolution は \(\displaystyle c_k = \bigvee_{i\circ j=k}(a_i \land b_j)\)、*_and_convolution は \(\displaystyle c_k = \bigwedge_{i\circ j=k}(a_i \lor b_j)\) です。各 bit を独立に計算します。

添字 \(\circ\) OR で集約 AND で集約 計算量
\(i\mathbin{\&}j\) and_or_convolution(a,b) and_and_convolution(a,b) \(O(WN\log N)\)
\(i\mathbin{\vert}j\) or_or_convolution(a,b) or_and_convolution(a,b) \(O(WN\log N)\)
\(i\mathbin{\oplus}j\) xor_or_convolution(a,b) xor_and_convolution(a,b) \(O(WN\log N)\)
\(\gcd(i,j)\) gcd_or_convolution(a,b) gcd_and_convolution(a,b) \(O(WN\log\log N)\)
\(\operatorname{lcm}(i,j)\) lcm_or_convolution(a,b) lcm_and_convolution(a,b) \(O(WN\log\log N)\)
\(i\mathbin{\vert}j\)、\(i\mathbin{\&}j = 0\) subset_or_convolution(a,b) subset_and_convolution(a,b) \(O(WN\log^2 N)\)

関数名の前半は添字の操作、後半は係数をまとめる操作です。and_or_convolution なら、全ての組 i,j について c[i & j] |= a[i] & b[j] とする結果になります。AND で集約する版は、各出力先を全 bit が 1 の値で初期化してから &= a[i] | b[j] します。該当する組がない添字には、その初期値が残ります。

係数は bool または 64 bit 以下の整数です。GCD・LCM では添字 0 を使用しません。出力長は GCD・LCM が長い方の長さ、それ以外はそれを 2 の冪に切り上げた長さ \(N\) です。範囲外の LCM は出力しません。\(N \le 2^{30}\) とします。

係数の bit 数を \(W\) として、時間は AND・OR・XOR が \(O(WN \log N)\)、GCD・LCM が \(O(WN \log \log N)\)、Subset が \(O(WN \log^{2} N)\) です。追加空間は Subset が \(O(N \log N)\)、その他が \(O(N)\) です。

依存ライブラリ

コード

#ifndef BOOLEAN_CONVOLUTION_HPP
#define BOOLEAN_CONVOLUTION_HPP

#include <algorithm>
#include <bit>
#include <cassert>
#include <cstddef>
#include <limits>
#include <type_traits>
#include <vector>

#include "bitwise_convolution.hpp"
#include "gcd_lcm_convolution.hpp"
#include "subset_convolution.hpp"

namespace boolean_convolution_detail {
template <int index, bool dual = false, class T>
std::vector<T> multiply(const std::vector<T> &a, const std::vector<T> &b) {
	static_assert(std::is_integral_v<T> && sizeof(T) <= 8 && 0 <= index && index <= 5);
	if (a.empty() || b.empty()) return {};
	using U = std::make_unsigned_t<std::conditional_t<std::is_same_v<T, bool>, unsigned char, T>>;
	std::size_t n = std::max(a.size(), b.size());
	if constexpr (index < 3 || index == 5) n = std::bit_ceil(n);
	assert(n <= std::size_t(1) << 30);
	constexpr U full = std::is_same_v<T, bool> ? U(1) : U(~U(0));
	U mask = 0, active = 0;
	for (std::size_t i = index == 3 || index == 4; i < a.size() && mask != full; ++i) {
		U value = U(a[i]);
		mask |= U(dual ? ~value : value) & full;
	}
	for (std::size_t i = index == 3 || index == 4; i < b.size() && active != mask; ++i) {
		U value = U(b[i]);
		active |= U(dual ? ~value : value) & mask;
	}
	std::vector<T> result(n, dual ? T(~U(0)) : T(0));
	for (; active; active &= active - 1) {
		int bit = std::countr_zero(active);
		std::vector<__int128_t> x(n), y(n);
		for (std::size_t i = index == 3 || index == 4; i < a.size(); ++i) x[i] = ((U(a[i]) >> bit) & 1) ^ dual;
		for (std::size_t i = index == 3 || index == 4; i < b.size(); ++i) y[i] = ((U(b[i]) >> bit) & 1) ^ dual;
		std::vector<__int128_t> counts;
		if constexpr (index < 3) counts = bitwise_convolution_detail::multiply<index>(std::move(x), std::move(y));
		else if constexpr (index == 5) counts = subset_convolution(std::move(x), std::move(y));
		else counts = gcd_lcm_convolution_detail::multiply<index == 3>(std::move(x), std::move(y));
		for (std::size_t i = 0; i < n; ++i)
			if (counts[i]) {
				U flag = U(1) << bit;
				result[i] = T(dual ? U(result[i]) & ~flag : U(result[i]) | flag);
			}
	}
	if constexpr (index == 3 || index == 4) result[0] = T(0);
	return result;
}
}

template <class T>
std::vector<T> and_or_convolution(const std::vector<T> &a, const std::vector<T> &b) { return boolean_convolution_detail::multiply<0>(a, b); }
template <class T>
std::vector<T> or_or_convolution(const std::vector<T> &a, const std::vector<T> &b) { return boolean_convolution_detail::multiply<1>(a, b); }
template <class T>
std::vector<T> xor_or_convolution(const std::vector<T> &a, const std::vector<T> &b) { return boolean_convolution_detail::multiply<2>(a, b); }
template <class T>
std::vector<T> gcd_or_convolution(const std::vector<T> &a, const std::vector<T> &b) { return boolean_convolution_detail::multiply<3>(a, b); }
template <class T>
std::vector<T> lcm_or_convolution(const std::vector<T> &a, const std::vector<T> &b) { return boolean_convolution_detail::multiply<4>(a, b); }
template <class T>
std::vector<T> and_and_convolution(const std::vector<T> &a, const std::vector<T> &b) { return boolean_convolution_detail::multiply<0, true>(a, b); }
template <class T>
std::vector<T> or_and_convolution(const std::vector<T> &a, const std::vector<T> &b) { return boolean_convolution_detail::multiply<1, true>(a, b); }
template <class T>
std::vector<T> xor_and_convolution(const std::vector<T> &a, const std::vector<T> &b) { return boolean_convolution_detail::multiply<2, true>(a, b); }
template <class T>
std::vector<T> gcd_and_convolution(const std::vector<T> &a, const std::vector<T> &b) { return boolean_convolution_detail::multiply<3, true>(a, b); }
template <class T>
std::vector<T> lcm_and_convolution(const std::vector<T> &a, const std::vector<T> &b) { return boolean_convolution_detail::multiply<4, true>(a, b); }

template <class T>
std::vector<T> subset_or_convolution(const std::vector<T> &a, const std::vector<T> &b) { return boolean_convolution_detail::multiply<5>(a, b); }
template <class T>
std::vector<T> subset_and_convolution(const std::vector<T> &a, const std::vector<T> &b) { return boolean_convolution_detail::multiply<5, true>(a, b); }

#endif
Back to top page