前提・実現したいこと
nを入力として受け取り、n! を 1000000007(10^9 + 7)で割った余りを表示するプログラムを作ろうとしています。nの大きさとしては10万以下を想定しているのですが、nが数万程度以上になると急にプログラムが正常に動作しなくなるという問題が発生しています。原因を突き止めて正常に動作させたいと思っています。
発生している問題・エラーメッセージ
n = 26033 までは正常に動作するのですが、n が26034以上になると急にプログラムが正常に動作しなくなります。具体的には出力まで達することなくプログラムが終了するようになります。
(8/27追記)
皆様ご回答ありがとうございます。お返事が遅くなってしまい大変申し訳ありません。皆様のアドバイスをもとにループを使う方式と、末尾再帰を用いる方式の2パターンで試してみました。末尾再帰を用いる方式ではn が100万を超えるような大きさの問題でも途中で止まることなく計算ができるようです。一方でループを用いる方式では、n が20万程度のところでループがストップしてしまうようです。どちらにせよ今回はn が10万以下の想定なので問題はありませんが、後学のためにこのような差異が表れた原因についてお教えいただけますと幸いです。
最初に作成した末尾再帰のコードは配列に階乗の値を格納することができなかったため、配列に値を格納できるよう末尾再帰を書き直しました。しかしこのコードもまたn が10万強のところでストップしてしまうようです。
大変初歩的なミスでお恥ずかしい限りなのですが、回答者様からご指摘をいただき MAX_N を書き換えていなかったことが原因と分かりました。そのために配列へのアクセスのある関数は途中で止まってしまったようです。MAX_N をもっと大きな値で書き換えた場合、MAX_N 以下の n では末尾再帰、ループともに正常に動作することを確認いたしました。
該当のソースコード
C++
1#pragma GCC target("avx2") 2#pragma GCC optimize("O3") 3#pragma GCC optimize("unroll-loops") 4 5#include <algorithm> 6#include <iostream> 7 8const long long MOD = 1E+09 + 7; 9const int MAX_N = 100000; 10 11using namespace std; 12 13long long modfac[MAX_N + 1]; 14long long modfacinv[MAX_N + 1]; 15 16long long modPow(long long x, long long a); 17long long modfactor(long long n); 18 19int main() 20{ 21 ios::sync_with_stdio(false); 22 cin.tie(nullptr); 23 24 long long n; 25 26 cin >> n; 27 cout << "(" << n << "!) % " << MOD << " = " << modfactor(n) << "\n"; 28 29 return 0; 30} 31 32// x^a をMODで割った余りを求める 33long long modPow(long long x, long long a) 34{ 35 if (a == 1) 36 return x % MOD; 37 if (a % 2 == 0) { 38 long long t = modPow(x, a / 2); 39 return (t * t) % MOD; 40 } else { 41 return ((x % MOD) * modPow(x, a - 1)) % MOD; 42 } 43} 44 45 46// n! をMODで割った余りを求める 47long long modfactor(long long n) 48{ 49 if (n == 0) { 50 modfacinv[n] = 1; 51 return modfac[n] = 1; 52 } 53 modfac[n] = (n * modfactor(n - 1)) % MOD; 54 modfacinv[n] = (modPow(n, MOD - 2) * modfacinv[n - 1]) % MOD; 55 return modfac[n]; 56} 57
試したこと
関数 modfactor の中の modfacinv[n] = (modPow(n, MOD - 2) * modfacinv[n - 1]) % MOD; の記述を消したところ n = 65139 まで正常に計算できるようになりました。このプログラムでは modfacinv は使っていませんが、元々コンビネーションの計算用に作っていたプログラムなのでこちらも計算できるようにしたいと思っています。
###(8/27追記)
末尾再帰を用いる方式
C++
1long long modfactor(long long n, long long f) 2{ 3 if (n == 1) { 4 return f; 5 } 6 return modfactor(n - 1, (n * f) % MOD); 7}
末尾再帰+配列格納(modFactor(n, 0, 1) を呼ぶとn! までの階乗とその逆元が全て求まるようにしました)
C++
1long long modFactor(long long n, long long i, long long f) 2{ 3 modfac[i] = f; 4 if (n == i) { 5 modfacinv[n] = modPow(modfac[n], MOD - 2); 6 modFactorInv(n, modfacinv[n]); 7 return f; 8 } 9 return modFactor(n, i + 1, ((i + 1) * f) % MOD); 10} 11 12long long modFactorInv(long long i, long long f) 13{ 14 if (i == 0) { 15 modfacinv[i] = f; 16 return f; 17 } 18 modfacinv[i] = f; 19 return modFactorInv(i - 1, (i * f) % MOD); 20}
ループを用いる方式((M - 1)! の逆元がM(M!)^-1 で求められることを用いています)
C++
1// n!を求める 2long long modfactor(long long n) 3{ 4 modfac[0] = 1; 5 long long p = 1; 6 while (p <= n) { 7 //cout << p << "\n"; 8 modfac[p] = (p * modfac[p - 1]) % MOD; 9 p++; 10 } 11 modfacinv[n] = modPow(modfac[n], MOD - 2); 12 modfactorInv(n); 13 return modfac[n]; 14} 15 16// (n!)^-1 を求める 17void modfactorInv(long long n) 18{ 19 long long p = n - 1; 20 while (p >= 0) { 21 modfacinv[p] = ((p + 1) * modfacinv[p + 1]) % MOD; 22 p--; 23 } 24}
回答3件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/08/21 14:05