yukicoder No.109 N! mod M

https://yukicoder.me/problems/no/109

問題概要

T個の数の組(N, M)が与えられるので、それぞれでN! mod Mを求めよ

  • 1 <= T <= 100
  • 1 <= M <= 109
  • max(0,M−105) <= N <= 109

解法

まず自明なケースとして、NがMより大きい場合答えは0である(N!で掛ける数の中に必ずMが登場するため)。以下では N < Mとして考える。

サンプルを見ると、N=999999936, M=999999937 で答えが 999999936 というのが目につく。もしかしてMが素数ならば (M-1)! ≡ M-1 (mod M) というような法則でも成り立っているのだろうか…と思っていくつかの素数で実験してみると、どうやら当たっているように思える(実際にはウィルソンの定理として知られている)。この仮定に基づくとMが素数の場合にはM-1から始めてどんどん逆元をたどっていけば求めることが可能になる。ここで N >= M-105 という怪しげな制約が生きてきて、この遡りが105ステップ以内に終了することを保証してくれる。これでMが素数の場合は解けた。

次にMが合成数の場合。まずMが十分大きければ答えは必ず0になる。例えば M >= 106 とすると、制約より N >= 106-105 になる。ここでMを2以上の正整数2つ(m1, m2)の積で表すことにすると、その最大値は M/2 である。M > N > M/2 より m1, m2がともにN!の途中に現れることになるので、答えは0になる。一方、Mがそれより小さい場合には普通にmodをとりながら階乗を計算していけば答えを出すことができる。

感想

サンプルが優しいおかげでできた

コード (D言語)

import std.stdio, std.array, std.string, std.conv, std.algorithm;
import std.typecons, std.range, std.random, std.math, std.container;
import std.numeric, std.bigint, core.bitop;

bool is_prime(long n) {
    for (long i = 2; i * i <= n; ++i)
        if (n % i == 0)
            return false;
    return true;
}

long powmod(long a, long x, long m) {
    long ret = 1;
    while (x) {
        if (x % 2) ret = ret * a % m;
        a = a * a % m;
        x /= 2;
    }
    return ret;
}

long solve() {
    auto s = readln.split.map!(to!long);
    auto N = s[0];
    auto M = s[1];

    if (M == 1)
        return 0;
    if (N == 0)
        return 1;
    if (N >= M)
        return 0;

    if (is_prime(M)) {
        long ret = M - 1;
        for (long cnt = M - 1; cnt > N; --cnt)
            ret = ret * powmod(cnt, M-2, M) % M;
        return ret;
    } else if (M < 10^^6) {
        long ret = 1;
        for (long i = 2; i <= N; ++i)
            ret = ret * i % M;
        return ret;
    } else {
        return 0;
    }
}

void main() {
    auto T = readln.chomp.to!int;
    while (T--)
        solve.writeln;
}