AtCoder Beginner Contest 349 F問題メモ
F - Subsequence LCM
問題
- 長さ N の数列 A=(A1,...,AN) がある
- ここから1つ以上の要素を選んで、その最大公約数がちょうど M になるようにしたい
- 選び方の個数を mod998244353 で求めよ
- A には同じ値も含まれうるが、異なる位置から採用したものは別物として数える
- 1≤N≤2×105
- 1≤M≤1016
解法1
最大公約数を考えるには、まず M を素因数分解する。
M≤1016 なので O(√M) の素因数分解はやや厳しく見えるが、演算が単純なのでできる。
不安なら確率的になるが高速なポラード・ロー法を使うこともできる。
たとえば M=1176=233172 とできる場合、
- M を割り切ることができない Ai は採用できないので、はじめから除いて考えてよい
- 23 を素因数に含む Ai を最低1つ含む必要がある
- 31 を素因数に含む Ai を最低1つ含む必要がある
- 72 を素因数に含む Ai を最低1つ含む必要がある
これさえ満たせば、最小公倍数が M になる。
すると、少し特殊なナップサック問題みたいに言い換えられる。
まず、M の素因数の種類数を K(今回は 3)として、各 Ai をもとに長さ K の bitflag Bi を作る。
- Ai が 23 を素因数に含むとき、Bi の1の位に1を立てる
- Ai が 31 を素因数に含むとき、Bi の10の位に1を立てる
- Ai が 72 を素因数に含むとき、Bi の100の位に1を立てる
すると、「Bi からいくつかを選んで、その bitwise-or が長さ K の 111…1
となるような選び方の個数」を数えればよいことになる。
このとき、2×3×5×7×... のように 素数を小さい方からかけると 14 個で 1016 を超えるので、素因数の種類数は最大でも K≤13 となる。
213=8192 なので、普通にナップサックDPすると O(N2K)≃1.6×109 となりダメだが、
種類数が少ないことを利用して Bi が同じものはまとめて遷移させると O(4K)≃6.7×107 で求められる。
解法2
bitwise-or のところまでは解法1と同じだが、ゼータ変換・メビウス変換を使えばナップサックより高速に求められる。
Bi ごとに、個数をカウントした配列 000 001 010 011 100 101 110 111 [ 3 2 0 1 2 3 0 0 ] ↓ゼータ変換: たとえば 110 の値は、Bi=000,010,100,110 のように110の部分集合であるBiの個数の和 000 001 010 011 100 101 110 111 [ 3 5 3 6 5 10 5 11 ] ↓2の冪乗にする: たとえば 110 の値は、bitwise-orが110の部分集合となるようなBiの選び方の個数 000 001 010 011 100 101 110 111 [ 8 32 8 64 32 1024 32 2048 ] ↓メビウス変換: たとえば 110 の値は、bitwise-orが "ちょうど" 110となるようなBiの選び方の個数 000 001 010 011 100 101 110 111 [ 8 24 0 32 24 968 0 992 ]
よって、この例の答えは 992 となる。