AtCoder Grand Contest 058 B問題メモ
B - Adjacent Chmax
問題
- 1~N の順列 P=(P1,...,PN)
- 「隣り合う2要素 Pi,Pi+1 を、max に置き換える」という操作を何度でも行える
- 操作後の P としてあり得る数列の個数を 998244353 で割ったあまりで求めよ
- 2 \le N \le 5000
解法
変なDP。
性質を考えると「各数字は、自身より小さい値が続く限りは好きなだけ左右に浸食できるよ」と言い換えられる。
1 7 2 3 6 4 8 5 ↓ 1 7 6 6 6 6 8 5
この時、後からより大きい値に上書きされると、当初の位置から離れた位置にもその数字が来うるのが、少し見落としやすい。
1 7 6 6 6 6 8 5 ↓ 1 7 [6] 8 8 8 8 5 当初の位置と全く違う位置に来た6
区間DPや、最大値または最小値からパターン数を求めていくのも考えたが、あんまり上手くいかない。
結局、左から詰めていくのがよさげ。
- DP[i][j]= 左から i 項だけで考えたとき、i 項目が j であるような数列の個数
この時、右側の数字が遡って左側に浸食しうるというのが難点だが、そこは i を遡って更新する。
いろんな遷移パターンを、注意深く見つけていく必要がある。
左端に“0”があったと見なした方が処理しやすいので、そうする。
i 0 1 2 3 4 5 6 7 8 9 10 j \ P (0) 4 9 6 3 8 10 1 2 7 5 --------------------------------------- (0) 1 1 2 3 4 5 6 7 8 9 10 ---------------------------------------- 計 1
i 列を更新するとき、(i+1 以降は後から更新するので保留として)
- P_i を使う。P_i は左側にも遡って浸食しうる
- i-1 以前から浸食してきた数字を i にも浸食させる
の2つとなる。
P_i を使う
左にどこまで浸食できるかをまず求める。遡って見ていって、P_i より大きいのが初めて出てきた位置を k とする。
存在しない場合は k=0 とする。
(以下の表はまだ説明してない更新があるが、ひとまず所与として、今は i=5 について更新する)
k i i 0 1 2 3 4 5 6 7 8 9 10 j \ P (0) 4 9 6 3 8 10 1 2 7 5 --------------------------------------- (0) 1 1 2 3 4 4 1 5 6 2 4 7 8 ? ? ? ? の位置を更新する 9 1 2 2 2 10 ---------------------------------------- 計 1 2 2 4 10
l=k+1~i について、DP[l][8] を求めていく。
求めたい箇所の手前(l-1)は何で終わっていようと問題ないので、DP[l][8] にはその時点の DP[l-1] の「計」がそのまま入ることになる。
l=k+2 以降では、より左まで自身が浸食したパターンも含まれるので、自身による直前の更新も含めて加算する。
k i i 0 1 2 3 4 5 6 7 8 9 10 j \ P (0) 4 9 6 3 8 10 1 2 7 5 --------------------------------------- (0) 1 1 2 3 4 4 1 5 6 2 4 7 8 [2] 9 1 2 2 2 10 ---------------------------------------- 計 1 2 [2] 6 10 i 0 1 2 3 4 5 6 7 8 9 10 j \ P (0) 4 9 6 3 8 10 1 2 7 5 --------------------------------------- (0) 1 1 2 3 4 4 1 5 6 2 4 7 8 2 [6] 9 1 2 2 2 10 ---------------------------------------- 計 1 2 2 [6]16 i 0 1 2 3 4 5 6 7 8 9 10 j \ P (0) 4 9 6 3 8 10 1 2 7 5 --------------------------------------- (0) 1 1 2 3 4 4 1 5 6 2 4 7 8 2 6[16] 9 1 2 2 2 10 ---------------------------------------- 計 1 2 2 6[16]16
浸食してきた値を使う
i=5 の更新を見る。今は浸食する更新のみを考える。
i i 0 1 2 3 4 5 6 j \ P (0) 6 5 4 2 3 1 --------------------------- (0) 1 1 2 5 3 4 2 5 ? ? の位置を更新する 5 1 2 3 ? 6 1 1 1 1 ? ---------------------------- 計 1 1 2 5 14
DP[i-1] に値のあるうち、4,5,6 の3つの数字が浸食しうる。2 は 3 より小さいため浸食し得ない。
性質を考えると、これは初期の P の中では降順(6→5→4 の順)に出現しているはずである。
この内、最大である j=6 は、自身が i まで来ようと思うとその間の全てを 6 で覆い尽くすしかないので、 DP[i-1][6] がそのまま DP[i][6] に継承される。
一方 j=5 は、冒頭で「見落としがち」といったような、「5 を先に i=5 まで浸食させといて、後で 6 を i=4 まで浸食させる」というものもあり得る。
6 5 4 2 3 ... ↓ 6 5 5 5 5 ... ↓ 6 6 6 6 5 ...
これも「i 番目の項が 5 である数列」として数えなければならない。
よって、DP[i][5] には自身の継承分 DP[i-1][5] に加え、DP[i-1][6] からも遷移する。
一方で、DP[i-1][4] からは遷移しない。もしそうなると、P の初期値と 4,5 の位置関係が逆転していることになり、そういうことは起こらない。
「自身より大きい、1つ前の値」から遷移できることになる。
従って j=4 は同様に考えると、DP[i][4]=DP[i-1][4]+DP[i-1][5]+DP[i-1][6] となる。
これは、j=N~(P_i+1) について DP[i-1][j] に値が入っているところのみで、後ろから累積和を取ることで計算できる。
i i 0 1 2 3 4 5 6 j \ P (0) 6 5 4 2 3 1 --------------------------- (0) 1 1 2 5 3 4 2 5 9 5 1 2 3 4 6 1 1 1 1 1 ---------------------------- 計 1 1 2 5 14 14
DPの更新は、この「P_i を使う」「浸食してきた値を使う」の2パターンを両方行えばよい。
「P_i を使う」では横方向、「浸食してきた値を使う」では縦方向に それぞれ最大 N 回の更新・累積和の計算などを行うのみなので、 「計」もちゃんと管理しておくことで、計算量は O(N^2) となる。
全て埋め終えたサンプル3は以下のようになる。
i 0 1 2 3 4 5 6 7 8 9 10 j \ P (0) 4 9 6 3 8 10 1 2 7 5 ------------------------------------------- (0) 1 1 45 2 45 135 3 4 4 1 5 405 6 2 4 7 45 180 360 405 8 2 6 16 9 1 2 2 2 2 10 1 3 5 11 27 45 45 45 45 45 -------------------------------------------- 計 1 3 5 11 27 45 45 180 360 405 855