Day 054-Q4 — EGF + FPS exp(置換の数え上げ)

2026-06-07 赤色 Master / Phase 8+ ★★★★★★★★★ EGF / 指数型母関数 / FPS exp / NTT / 置換のサイクル分解

問題

$n$ 個の要素の置換 $\sigma \in S_n$ のうち、すべてのサイクルの長さが集合 $A$ に属する ものの個数を $\bmod 10^9+7$ で求めよ。

制約

パラメータ範囲
$n$$1 \le n \le 10^5$
$|A|$$1 \le |A| \le n$
$A$ の要素$1 \le a_i \le n$
出力$\bmod 10^9+7$

入出力例

入力例 1

6
2
1 2

出力例 1

76

サイクル長が 1 か 2 のみ(対合: $\sigma^2 = \text{id}$)の $S_6$ 置換の個数。対合の個数は $\sum_{k=0}^{\lfloor n/2 \rfloor} \binom{n}{2k}(2k-1)!!$。

概念図: 置換のサイクル分解と EGF

置換のサイクル分解: σ=(1 3 5)(2 4)(6) ∈ S_6 長さ3のサイクル 1 3 5 長さ2のサイクル 2 4 長さ1のサイクル (不動点) 6 EGF の公式 長さℓのサイクルの EGF: x^ℓ / ℓ 許可長の EGF の和: f(x) = Σ_{ℓ∈A} x^ℓ/ℓ 置換全体の EGF: G(x) = exp(f(x)) 答え = [x^n]G(x) × n!

ヒント(段階的開示)

ヒント1: 方向性
置換の指数型母関数(EGF)を使う。EGF の「集合の exp 公式」: 「ラベル付き構造 A のマルチセット」の EGF = $\exp(\text{A の EGF})$。サイクルは置換を構成するマルチセットであり、長さ $\ell$ のサイクルの EGF は $x^\ell / \ell$($(\ell-1)! / \ell! \cdot \ell! \cdot [x^\ell]$)。
ヒント2: アプローチ
  • $f(x) = \sum_{\ell \in A, \ell \le n} x^\ell / \ell$ を $O(n)$ で構築(各 $\ell$ の mod 逆元を使用)
  • $G(x) = \exp(f(x)) \bmod x^{n+1}$ を FPS exp(Newton 法)で $O(n \log n)$ 計算
  • 答え = $[x^n] G(x) \times n! \bmod p$
ヒント3: FPS exp の Newton 法
# FPS exp の Newton 法(概要)
# g = exp(f) を満たす g を g_0=1 から反復
# g_{k+1} = g_k * (1 - log(g_k) + f) mod x^{2^{k+1}}
#
# 必要なサブルーチン:
# - poly_mul(a, b): NTT を使った多項式乗算
# - poly_inv(f, n): f の mod x^n での逆元
# - poly_deriv(f): 微分
# - poly_integ(f): 積分
# - poly_log(f, n): integral(f'/f) mod x^n
# - poly_exp(f, n): Newton 法で exp(f) mod x^n

模範解答 (Python)

import sys
input = sys.stdin.readline
MOD = 10**9 + 7

def ntt(a, invert=False):
    n = len(a)
    j = 0
    for i in range(1, n):
        bit = n >> 1
        while j & bit: j ^= bit; bit >>= 1
        j ^= bit
        if i < j: a[i], a[j] = a[j], a[i]
    length = 2
    while length <= n:
        w = pow(3, (MOD-1)//length, MOD)
        if invert: w = pow(w, MOD-2, MOD)
        for i in range(0, n, length):
            wn = 1
            for k in range(length//2):
                u = a[i+k]; v = a[i+k+length//2]*wn%MOD
                a[i+k] = (u+v)%MOD; a[i+k+length//2] = (u-v)%MOD
                wn = wn*w%MOD
        length <<= 1
    if invert:
        ni = pow(n, MOD-2, MOD)
        for i in range(n): a[i] = a[i]*ni%MOD

def poly_mul(a, b):
    rl = len(a)+len(b)-1; n = 1
    while n < rl: n <<= 1
    fa = a+[0]*(n-len(a)); fb = b+[0]*(n-len(b))
    ntt(fa); ntt(fb)
    for i in range(n): fa[i] = fa[i]*fb[i]%MOD
    ntt(fa, True); return fa[:rl]

def poly_inv(f, n):
    g = [pow(f[0], MOD-2, MOD)]; size = 1
    while size < n:
        size <<= 1
        fg = poly_mul(f[:size], g)[:size]
        fg = [(-x)%MOD for x in fg]; fg[0] = (fg[0]+2)%MOD
        g = poly_mul(g, fg)[:size]
    return g[:n]

def poly_log(f, n):
    df = [(i*f[i])%MOD for i in range(1, n)]
    fi = poly_inv(f, n)
    r = poly_mul(df, fi)[:n-1]
    inv = [0]*n; inv[1] = 1
    for i in range(2, n): inv[i] = -(MOD//i)*inv[MOD%i]%MOD
    return [0]+[r[i]*inv[i+1]%MOD for i in range(n-1)]

def poly_exp(f, n):
    g = [1]; size = 1
    while size < n:
        size <<= 1
        lg = poly_log(g, size)
        h = [0]*size
        for i in range(min(len(f), size)): h[i] = (h[i]+f[i])%MOD
        for i in range(size): h[i] = (h[i]-lg[i])%MOD
        h[0] = (h[0]+1)%MOD
        g = poly_mul(g, h)[:size]
    return g[:n]

def solve():
    n = int(input())
    k = int(input())
    A = set(map(int, input().split()))
    f = [0]*(n+1)
    for a in A:
        if a <= n: f[a] = pow(a, MOD-2, MOD)
    g = poly_exp(f, n+1)
    fact = 1
    for i in range(1, n+1): fact = fact*i%MOD
    print(g[n]*fact%MOD)

solve()

Step-by-Step 解説

1置換の EGF 理論
置換は「サイクルの集合」として一意に分解される(cycle decomposition)。EGF の積公式: 「ラベル付き構造Aのマルチセット」の EGF = $\exp(\text{A の EGF})$。長さ $\ell$ のサイクルの EGF は $x^\ell / \ell$。
2FPS exp の Newton 法
$g = e^f$ を満たす $g$ を $g_0=1$ から反復: $g_{k+1} = g_k(1 - \ln g_k + f) \bmod x^{2^{k+1}}$。各ステップ $O(n \log n)$、合計 $O(n \log n)$。
3FPS log の実装
$\ln g = \int (g'/g) \, dx$。微分 $O(n)$、逆元 $O(n \log n)$、積 $O(n \log n)$、積分 $O(n)$。全体 $O(n \log n)$。
4係数から答えを求める
EGF の定義より $[x^n] G(x) = (\text{n 元構造の個数}) / n!$。よって答え = $[x^n] G(x) \times n! \bmod p$。

計算量

FPS exp: $O(n \log n)$
NTT(1回): $O(n \log n)$、定数 $\approx 5$
全体: $O(n \log n)$ — $n = 10^5$ で約 $10^7$ 演算

よくあるミス

ミス原因正しい書き方
f[0] != 0 のまま exp を呼ぶ$\exp(c + \ldots)$ は $f[0]=0$ が前提サイクル長は $\ge 1$ なので $f[0]=0$ 自動的に成立
NTT サイズが足りないpoly_mul の結果が切れる常に $\text{len}(a)+\text{len}(b)-1$ 以上の 2 冪
微分で次数ずれ$[x^i]$ ではなく $[x^{i-1}]$ に書くpoly_deriv(f)[i] = (i+1)*f[i+1]
$n!$ を掛け忘れEGF の係数は個数 / $n!$g[n] * fact % MOD

次のステップ

  • 発展問題: サイクル長が偶数のみ / 奇数のみの置換の個数($\cosh$, $\sinh$ の EGF)
  • 関連: Bell 数(集合分割)= $[x^n] \exp(e^x - 1) \times n!$
  • 応用: 有向グラフの巡回置換数え上げ(Burnside 補題 + EGF)

自己評価