バチャでは手が回らなかったけど、復習した。ちょっとこの多項式解法は今はよくわからないけど、覚えておこう...
問題概要
要素の数列 が与えられる。
の順列 に対して、それを巡回群の直積として表したときの、各巡回群に含まれる要素に対応する の値のうち最小の値の積をとったものを とおく。
各 に対して、巡回群の個数が 個となるような順列 についての の積を とする。
の最大公約数を mod. 998244353 で求めよ。
制約
考えたこと
なんだかすごくごっちゃりした設定だけど、最近 AOJ 2378 SolveMe を解き直したこともあって、巡回群来たーーー!!!!!!!!!!!!という気持ちになった。
とりあえず を小さい順にソートしてしまう!!!
そして巡回群を順に形成していくときに、「まだ選んでないもののうち最小のものを選ぶ」という風に順序を固定しておく。
とりあえず挿入 DP
巡回群を挿入していく DP、要素を 1 個ずつ追加するか、まとめて 個 (巡回群の位数) を追加するか選択の余地はあるけど、今回は が小さい順にソートされているので、すでにある巡回群に要素を追加しても変わらない。よって、1 個ずつ追加する形で書いてみよう。
- dp[ i ][ j ] := i 個の要素で巡回群を形成していったときに j 個の巡回群ができている場合の、f(p) の積の総和
とする。ここから第1種スターリング数を数え上げるような DP をする。そして、この種の DP では「新たな要素をどの巡回群に加えるか」という視点で考える
- dp[ i + 1 ][ j + 1 ] = a[ i ] × dp[ i ][ j ] (新たな要素で新たな巡回群を作る場合)
- dp[ i + 1 ][ j ] = i × dp[ i ][ j ] (すでにある巡回群に挿入する場合)
という DP になる。a[ i ] = 1 とすれば、第1種スターリング数を求める方法の個数を表す漸化式そのものである。第1種スターリング数に限らず、分割数や (第2種) スターリング数など、グループ分けに関する DP において
- 新たに追加する要素を新たなグループとするか
- 新たに追加する要素を既存のグループに挿入するか
で場合分けして DP を組むのは典型的ではある。そして答えは、dp[ N ][ 1 ], dp[ N ][ 2 ], ..., dp[ N ][ N ] の最大公約数なのだが...
dp を多項式化する
これはどうなんだろう...解けている人は実験して導いているのだろうか...
- = dp[ ][ 0 ] + dp[ ][ 1 ] + dp[ ][ 2 ] +
という多項式を考える。ここまでは FFT とかでも見る考え方ではある。なにか詰まったときは機械的にこういう多項式を考えるようにしてもいいかもしれない。DP の遷移
- dp[ i + 1 ][ j ] = × dp[ i ][ j - 1 ] + i × dp[ i ][ j ]
は機械的に次のようになる。
- [ ] () = [ ] () + [ ] ()
そうすると、dp 遷移を次のように解釈できる。
- =
よって、dp[ N ] は、
の各係数になる。こうなると原始多項式に関する理論を適用できる。
原始多項式
原始多項式とは、各係数の最大公約数が 1 であるような多項式のこと。原始多項式同士の積もまた、原始多項式であるという有名な定理がある。このことから、
各係数の最大公約数が である多項式 と、各係数の最大公約数が である多項式 があるとき、
の各係数の最大公約数は である
ということがいえる。元の問題に戻って、答えは
となる。うーん...ここまで自力で解ける気がしないやが ^^;
#include <iostream> #include <vector> #include <algorithm> using namespace std; const int MOD = 998244353; long long GCD(long long x, long long y) { if (y == 0) return x; else return GCD(y, x % y); } int main() { int N; cin >> N; vector<long long> a(N); for (int i = 0; i < N; ++i) cin >> a[i]; sort(a.begin(), a.end()); long long res = 1; for (int i = 0; i < N; ++i) { res = (res * GCD(a[i], i)) % MOD; } cout << res << endl; }