vocabulary の解説 と同様に N×(N−1)×(N−2)×⋯×(N−i+1) を P(N,i) と表記します。
解説
入力の受け取りには多倍長整数を使用するか、(N を 1018 で割った余りしか出力に影響しないことから) N を文字列として受け取り、その下 18 桁のみを 64 bit 整数に変換するなどの処理を行えばよいです。
vocabulary と同様、この問題も i=1∑NP(N,i) を 1018 で割った余りを求める問題となります。
ここで、任意の整数 m,n に対して m を 1018 で割った余りと (m+n⋅1018) を 1018 で割った余りは等しいことから、P(N,i) が 1018 の倍数となる場合にはその項の計算を行わなくても出力に影響しないことが分かります。そこで、P(N,i) が 1018 の倍数となる i の条件を見つけてその部分の計算を省略することを考えます。
P(N,i) は連続する i 個の整数の積であるため、1018≤i なる i について P(N,i) が 1018 の倍数となることは直ちに分かります(連続する 1018 個の整数の中には 1018 の倍数が含まれるため)。したがって、i=1∑min(N,1018−1)P(N,i) を計算するだけで答えが得られます。
もう少し考えると、1018=218×518 より連続する 1018 個の整数でなくても 2 の倍数と 5 の倍数がそれぞれ 18 個以上含まれるような区間の整数の積は 1018 の倍数となることが分かります。例えば連続する 90 個の整数に 2 の倍数と 5 の倍数はそれぞれ 18 個以上含まれるので、90≤i なる i について P(N,i) は 1018 の倍数となります。(更に言うと、連続する 25 個の整数の中には 25=52 の倍数が 1 つ含まれ、その分 5 の素因数を「稼げる」ため i の条件は 75≤i で十分です。)
以上より i=1∑min(N,74)P(N,i) を 1018 で割った余りを計算すればよく、この計算は十分高速に行えます。
解答例 (C++)
このようにすると、法とする値 (この問題では 1018) が i=1∏npiqi と素因数分解されるとき(整数のコピーや四則演算等の操作の時間計算量が O(1) であることを仮定すると) 時間計算量 O(1≤i≤nmaxpi⋅qi) でこの問題を解くことができます。
例えば「109 で割った余りを求めよ」という問題も同様に解くことができますが、「109+7 で割った余りを求めよ」という問題をこの方法で解いて実行時間制限に間に合わせることは厳しいです(109+7 は大きな素数であるため)。