kazuma8128’s blog

競プロの面白い問題を解きます

CodeChef IMGOD Apun Hi Bhagwan Hai !

問題概要

整数N (1 <= N <= 1e5) のみが与えられる.
分割する対象が区別できるものでの N の分割数 S(N, i) mod 163577857 (1 <= i <= N) をすべて求めよ.

解法

これは第二種スターリング数というものらしい.

S(n, k) = (1 / k!) * Σ(-1)^(k - m) * kCm * m^n = Σ ( (-1)^(k - m) / (k - m)! ) * ( m^n / m! )

という風に変形すると畳み込みの形で表せることがわかります.
なのでFFTをしたい気持ちになるけど値が大きいので誤差が怖い.

実はこの問題の mod の値 163577857 = 39 * 2^22 + 1 は NTT (高速剰余変換) が使える mod であることがググるとわかります. ちなみに最小の原始根は 23
なので整数のまま誤差なく計算できるので安心.

ソースコード

https://www.codechef.com/viewsolution/20267065

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

int mod_inv(ll a, ll m) {
    ll b = m, u = 1, v = 0;
    while (b > 0) {
        ll t = a / b;
        a -= t * b; swap(a, b);
        u -= t * v; swap(u, v);
    }
    return (u % m + m) % m;
}

ll mod_pow(ll x, ll y, ll md) {
    ll res = 1;
    while (y) {
        if (y & 1) res = res * x % md;
        x = x * x % md;
        y >>= 1;
    }
    return res;
}

template <int Mod, int PrimitiveRoot>
class fast_modulo_transform {
public:
    static vector<int> fft(vector<int> v, bool inv) {
        const int N = v.size();
        assert((N ^ (N & -N)) == 0);
        int ww = mod_pow(PrimitiveRoot, (Mod - 1) / N, Mod);
        if (inv) ww = mod_inv(ww, Mod);
        for (int m = N; m >= 2; m >>= 1) {
            const int mh = m >> 1;
            int w = 1;
            for (int i = 0; i < mh; ++i) {
                for (int j = i; j < N; j += m) {
                    const int k = j + mh;
                    int x = v[j] - v[k];
                    if (x < 0) x += Mod;
                    v[j] += -Mod + v[k];
                    if (v[j] < 0) v[j] += Mod;
                    v[k] = (1LL * w * x) % Mod;
                }
                w = (1LL * w * ww) % Mod;
            }
            ww = (1LL * ww * ww) % Mod;
        }

        int i = 0;
        for (int j = 1; j < N - 1; ++j) {
            for (int k = N >> 1; k > (i ^= k); k >>= 1);
            if (j < i) swap(v[i], v[j]);
        }
        if (inv) {
            const int inv_n = mod_inv(N, Mod);
            for (auto& x : v) {
                x = (1LL * x * inv_n) % Mod;
                assert(0 <= x && x < Mod);
            }
        }
        return v;
    }
    static vector<int> convolution(vector<int> f, vector<int> g) {
        int sz = 1;
        const int m = f.size() + g.size() - 1;
        while (sz < m) sz *= 2;
        f.resize(sz), g.resize(sz);
        f = fast_modulo_transform::fft(move(f), false); g = fast_modulo_transform::fft(move(g), false);
        for (int i = 0; i < sz; ++i) {
            f[i] = (1LL * f[i] * g[i]) % Mod;
        }

        return fast_modulo_transform::fft(move(f), true);
    }

    static int get_mod() {
        return Mod;
    }
};

const ll mod = 163577857;

using fmt = fast_modulo_transform<163577857, 23>;

int main()
{
    int N;
    cin >> N;
    vector<int> tmp(N + 1);
    tmp[0] = 1;
    for (int i = 1; i <= N; i++) {
        tmp[i] = (ll)tmp[i - 1] * i % mod;
    }
    tmp[N] = mod_inv(tmp[N], mod);
    for (int i = N; i >= 1; i--) {
        tmp[i - 1] = (ll)tmp[i] * i % mod;
    }
    vector<int> v1 = tmp, v2 = tmp;
    for (int i = 0, val = 1; i <= N; i++) {
        v1[i] = (ll)v1[i] * val % mod;
        val = (mod - val) % mod;
    }
    for (int i = 0; i <= N; i++) {
        v2[i] = (ll)v2[i] * mod_pow(i, N, mod) % mod;
    }
    auto res = fmt::convolution(v1, v2);
    for (int i = 1; i <= N; i++) {
        printf("%d%c", res[i], " \n"[i == N]);
    }
    return 0;
}

感想

これコンテスト中に通したかったなあ (第二種スターリング数であることまではわかってたので)
それはそうと NTT 速すぎてビビった.