確率や期待値を計算する DP をするときは、自己ループを除去することが多いね。
「EDPC J - ボール」などは類題と言える。先にこの問題を解いておくと、今回の問題も解きやすいかもしれない!
問題概要
以上 以下の整数が等確率で出るサイコロがある。
あなたは、今、整数 1 を持っている。この整数が 未満である間、次の操作を繰り返す。
- サイコロを振り、出た目を とする
- 持っている整数に を掛ける
最終的に持っている整数が に一致する確率を mod 998244353 で求めよ。
制約
考えたこと
この手の問題では DP がうまくいくことが多い。素朴に、次のような DP を考えてみる。
dp[i]
← 持っている整数が である状態から開始して、整数 に到達できる確率
このとき答えは dp[1]
となる。
dp[i]
を表すために、サイコロの目 の値で場合分けする。
サイコロの目 | その目が出る確率 | その後の持っている数 | その後 に到達できる確率 |
---|---|---|---|
dp[i] |
|||
dp[2i] |
|||
dp[3i] |
|||
dp[4i] |
|||
dp[5i] |
|||
dp[6i] |
よって、次のような DP が組める。
dp[i]
= dp[i]
+ dp[2i]
+ dp[3i]
+ dp[4i]
+ dp[5i]
+ dp[6i]
ただし注意したいことは、左辺と右辺の両方に dp[i]
が登場していることだ。このような現象を僕は自己ループと呼んでいる。式変形しよう。まず、dp[i]
を左辺に寄せる。
dp[i]
= dp[2i]
+ dp[3i]
+ dp[4i]
+ dp[5i]
+ dp[6i]
よって、次のようになる。
dp[i]
= dp[2i]
+ dp[3i]
+ dp[4i]
+ dp[5i]
+ dp[6i]
必要な DP 状態空間の大きさ
こうして DP が組めたが、 という制約が気になってくる。愚直にやると の計算量となって厳しいようにも思える。
しかし、そもそも が を割り切らないときは dp[i]
= 0 となるのだ。よって、 の約数であるような に対してのみ dp[i]
を考えればよい。
そして、 の約数の個数は高々 103680 個だ。次の記事を参照。
よって十分間に合う。
コード
#include <bits/stdc++.h> using namespace std; #include "atcoder/modint.hpp" using mint = atcoder::modint998244353; long long N; // 入力 map<long long, mint> dp; // メモ化用の配列 // メモ化再帰 mint rec(long long n) { if (n == N) return mint(1); if (dp.count(n)) return dp[n]; mint res = 0; for (int x = 2; x <= 6; ++x) { if (N % (n * x) == 0) { res += rec(n * x) / 5; } } return dp[n] = res; } int main() { cin >> N; cout << rec(1).val() << endl; }