思いついた解法が嘘解法で、正しい解法を思いつくためにかなり多量の実験をした。
No.770 Median Sequence - yukicoder
問題
正の整数Nが与えられる。要素が1からNまでの長さNの整数列bに対して、以下の操作で数列aを作る。
- a[1] = med(1, b[1], N)
- a[2] = med(med(1, b[1], N - 1), b[2], N)
- a[3] = med(med(med(1, b[1], N - 2), b[2], N - 1), b[3], N)
- ...
- a[i] = (m = 1; for(int j = 1; j <= i; ++j) m = med(m, b[i], N - i + j); を実行したときのmの値)
このとき、aとしてあり得るものの個数を数えよ。
解法
aとしてあり得るものは以下の操作で作れるものであり、およびそれに限る。
ここで、数列{c[1], ..., c[m]} が減少的であるとは、最後にc[i] < c[i+1]が成立する添字より最後にc[i] > c[i+1]が成立する添字が大きいことをいうことにする。
どちらかが存在しないときは、存在する方が大きいものとする。どちらも存在しないときはどちらも大きくないものとする。
- a[1] は任意
- 数列{a[1], ..., a[i - 1]}が減少的である場合、あるいはk = N - a[i - 1]としてi > k, a[i - k] = a[i - k + 1] = ... = a[i - 1]の両方が成り立つ場合、a[i] >= a[i - 1] - 1となるようにa[i]を選ぶ
- それ以外の場合、a[i] >= a[i - 1]となるようにa[i]を選ぶ
これは以下のようなDPで計算できる。
DP[i][j]: 長さiで最後の要素がjであるような、減少的でない数列の個数
DP2[i][j]: 長さiで最後の要素がjであるような、減少的である数列の個数
実装上の注意点
- 遷移が合っているかどうかを確認する
- 答えが合わなかったら愚直解法と比較する
提出: #305095 No.770 Median Sequence - yukicoder (Rust)
#305093 No.770 Median Sequence - yukicoder (愚直解法のコードを含む)
const MOD: i64 = 1_000_000_007; define_mod!(P, MOD); type ModInt = mod_int::ModInt<P>; fn solve() { let out = std::io::stdout(); let mut out = BufWriter::new(out.lock()); macro_rules! puts { ($format:expr) => (write!(out,$format).unwrap()); ($format:expr, $($args:expr),+) => (write!(out,$format,$($args),*).unwrap()) } input! { n: usize, } let mut dp = vec![vec![ModInt::new(0); n]; n + 1]; let mut dp2 = vec![vec![ModInt::new(0); n]; n + 1]; dp[0][0] = ModInt::new(1); for i in 1 .. n + 1 { let mut cur = ModInt::new(0); for j in 0 .. n { cur += dp[i - 1][j]; dp[i][j] += cur; cur += dp2[i - 1][j]; } for j in 0 .. n - 1 { let turn = n - 1 - j; if i >= turn { dp2[i][j] += dp[i - turn][j + 1] + dp2[i - 1][j + 1] + dp2[i - 1][j]; } } } let mut tot = ModInt::new(0); for i in 0 .. n { tot += dp[n][i] + dp2[n][i]; } puts!("{}\n", tot); }
まとめ
最初a[i]>=a[i-1]-1ならOKだと思ってサンプルが盛大に合わず苦しんだ。(そのような数列の個数はN=6のとき4488個ある。)