【AtCoder】ARC114C:Sequence Scores(600点)
問題
問題の概要
すべての項がである長さの数列に対して以下の操作を繰り返し、各項の値が以上以下の数列を作ることを考える。
- 区間とを選択し、各について、をで置き換える。
からを作るための最小回数をとおいたとき、すべての数列に対するの総和をで割った余りを求めよ。
制約
- は整数
解法
方針
を数列を作るためのコストと捉えて問題を考えてみます。
まず、数列の各項に対して1つ1つ左から順番に上記の操作を適用した場合を考えると、明らかに任意のに対して、回の操作でを作れることがわかります。作れる数列は通りなので、の和は以下です。
最悪でも操作に回しかかからないことがわかりましたが、実際にはもっとコストを削減できる場合もあるはずです。
関数を、項数がの場合以外にも拡張して考えてみます。
例えば、は明らかにですが、これにを追加したも同様にになります。
つまり、空の数列にの項を初項から順番に1つずつ追加していくことを考えたときに、条件次第では追加の前後での値は変わらない、すなわちノーコストで項が追加できることがわかります。
すべての数列について、ノーコストで項を追加できたインデックスを数え上げて、から引く方針で答えが出せそうな気がしてきます。
定式化
上の方針で出てきた“ノーコストで追加できる項”の個数を具体的に数式の形で表すことを考えてみましょう。
各に対し、
「番目の項としてを追加したときに、最小の操作回数が変化しなかったような長さの数列の個数」
と定義します。
”変化しなかった”と過去形で書いているのは、個の項すべてを追加し終わった後の最終的な数列の個数を数える必要があるためです。
すべてのについての和をとることにより、削減できるコストを重複・数え漏れなく数え上げることができます。
また、番目の項を追加する場合は必ずコストがかかるので、の場合は必ずとなり、は以上のときのみを考えればいいこともわかります。
以上より、関数の和を求める問題を、数列の数え上げの問題へ帰着させることができ、答えを
と表すことができました。
ノーコストで追加できる条件
定式化できたところで次に、最小の操作回数が変化しない(=ノーコストで追加できる)条件を調べてみましょう。
数列をブロックで表すこととして、いくつか例を載せてみます。
例:4番目の要素として、2を追加するとき()
上の例を観察すると、個目の要素としてを追加するときに、次のような条件を満たせばノーコストで追加できることがわかります。
① あるに対し、が成り立つ
② ①を満たす最大のに対して、「」が成り立つ
条件を図示すると以下のとおりです。
上の図の青い部分の数列の選び方の総数を左から順番に計算すると、それぞれとなります。
はの範囲を動くことから、は次の式で表すことができます。
以上の議論より、答えを以下の形で表せることがわかりました。
これはで計算できます。
高速化
このままではTLEしてしまうので、最後に計算量削減を行います。
先程の式の第2項は以下のように変形できます。
ここで、とおくと、これはすべてのに対して計算してもしかかかりません。
したがって、事前にすべてのを計算しておくことで、
と二重和の形で書くことができるため、全体としてまで削減できます。これは十分に高速なのでAC可能です。
なお、各式に出てくる累乗についてもその都度求めるのは時間がかかるため、これも事前に計算しておきましょう。
コード(PyPy3)
提出コード(PyPy3:1208ms)
PythonはTLEでした(涙)
def main(): # 入力、modの定義 N, M = map(int, input().split()) mod = 998244353 # 配列の事前計算(POW[i][j] = pow(i, j) % mod) POW = [[1]*5001 for _ in [0]*5001] for i in range(5001): tmp = 1 for j in range(1, 5001): tmp = tmp*i % mod POW[i][j] = tmp # s(i)の事前計算 s = [0]*(N-1) for i in range(N-1): tmp = 0 for m in range(M): tmp += POW[m][i] s[i] = tmp % mod # 計算(本体) ans = N*POW[M][N] % mod for n in range(2, N+1): for i in range(0, n-1): ans -= POW[M][N-i-2]*s[i] ans %= mod # 出力 print(ans) main()