cp-library

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

View the Project on GitHub qandori/cp-library

:heavy_check_mark: GCD-plus・LCM-plus 畳み込み

convolution/gcd_lcm_convolution.hpp · GitHub で見る

共通ページ

#include "lib/convolution/gcd_lcm_convolution.hpp"

std::vector<long long> a{0, 1, 2, 3}, b{0, 4, 5, 6};
auto c = gcd_convolution(a, b); // {0,62,10,18}
auto d = lcm_convolution(a, b); // {0,4,23,36}
操作 内容・返り値 計算量
gcd_convolution(a,b) \(\displaystyle c_k=\sum_{\gcd(i,j)=k}a_ib_j\) \(O(N\log\log N)\)
lcm_convolution(a,b) \(\displaystyle c_k=\sum_{\operatorname{lcm}(i,j)=k}a_ib_j\) \(O(N\log\log N)\)

値 1 からの配列を、先頭にダミーの 0 を置いて渡します。例えば a[2] は添字 2 の重みです。GCD 版では積 a[2]*b[3] を c[1] に加え、LCM 版では添字 6 が出力範囲内にある場合だけ加えます。入力を変更せず、入力と同じ係数型の配列を返します。

gcd_convolution は \(\displaystyle c_k = \sum_{\gcd(i,j)=k} a_i b_j\)、lcm_convolution は \(\displaystyle c_k = \sum_{\operatorname{lcm}(i,j)=k} a_i b_j\) を計算します。

係数は環の要素とし、\(a_0 = b_0 = 0\) で使います。出力長 \(N = \max(\lvert a\rvert,\lvert b\rvert)\) で、LCM が \(N\) 以上になる項は出力しません。

時間 \(O(N \log \log N)\)、追加空間 \(O(N)\) です。

依存ライブラリ

検証コード

コード

#ifndef GCD_LCM_CONVOLUTION_HPP
#define GCD_LCM_CONVOLUTION_HPP

#include "../components/gcd_convolution_mint.hpp"
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <type_traits>
#include <utility>
#include <vector>

namespace gcd_lcm_convolution_detail {

template <bool gcd, class T>
std::vector<T> multiply(std::vector<T> a, std::vector<T> b) {
	if (a.empty() || b.empty()) return {};
	assert(a[0] == T(0) && b[0] == T(0));
	std::size_t n = std::max(a.size(), b.size());
	a.resize(n, T(0));
	b.resize(n, T(0));
	auto ps = prime_sieve(n);
	if constexpr (gcd && std::is_same_v<T, mint_detail::Mint>) {
		if (n >= 4096 && n - 1 <= UINT32_MAX) {
			gcd_convolution_mint_detail::multiply(a, b, ps);
			return a;
		}
	}
	divisor_zeta_detail::for_each<false, gcd>(n, ps, ps.size(), [&](std::size_t to, std::size_t from) {
		a[to] += a[from], b[to] += b[from];
	});
	for (std::size_t i = 1; i < n; ++i) a[i] *= b[i];
	divisor_zeta<true, gcd>(a, ps);
	return a;
}
}

template <class T>
std::vector<T> gcd_convolution(std::vector<T> a, std::vector<T> b) {
	return gcd_lcm_convolution_detail::multiply<true>(std::move(a), std::move(b));
}
template <class T>
std::vector<T> lcm_convolution(std::vector<T> a, std::vector<T> b) {
	return gcd_lcm_convolution_detail::multiply<false>(std::move(a), std::move(b));
}

#endif
Back to top page