問題
https://codeforces.com/contest/1845/problem/E (diff: 2500)
個の箱が横一列に並んでおり、各箱は空またはボールが一個入っている。あなたは以下の操作を丁度回行う。
操作: ボールが入っている箱と空箱を一つ選ぶ。これらは隣接している必要がある。選んだ箱に入っているボールを空箱に移す。
操作後の箱の状態についてあり得る通り数を求めよ。
制約
解法
以下のようなdpを考える。
前から個目の箱を考慮した、現在個目の箱に「左からこの箱を通過しようとしているボールの数 - 右から既にこの箱を通過したボールの数」の値が、その状態に達する為に必要な最小の操作回数が回であるような通り数 *1
解はが奇数なら、偶数なら
dpの遷移は以下の様になる
個目の箱にボールを置く
が正のときはそのまま流れてきたボールを一個そこで捨てることになる。個のボールを番目の箱へ流すので操作回数が回増える。
が以下のときは右から追加で一個のボールを流してくる必要がある。番目の箱からの移動の寄与分操作回数が増える。
結局にを配ることになる。
個目の箱にボールを置かない
置く場合と同様に考えると、にを配ることになる。
普通にやるとである。しかし、以下のようなポイントがある。
- はで高々しか増減しない
- がを超えるような遷移は無視してよい
- が大体
このことからの添字を程度に減らすことができて、でできる。
実装
#include <bits/stdc++.h> #include "Src/Template/IOSetting.hpp" using namespace zawa; #include "atcoder/modint" using mint = atcoder::modint1000000007; int main() { SetFastIO(); int N, K; std::cin >> N >> K; constexpr int S{101}, O{50}; std::vector dp(S, std::vector<mint>(K + 1)); dp[O][0] = mint::raw(1); for (int i{} ; i < N ; i++) { int a; std::cin >> a; std::vector next(S, std::vector<mint>(K + 1)); for (int j{} ; j < S ; j++) { for (int k{} ; k <= K ; k++) { if (dp[j][k] == mint{}) continue; { int nj{j - 1 + a}; int nk{k + std::abs(nj - O)}; if (0 <= nj and nj < S and nk <= K) { next[nj][nk] += dp[j][k]; } } { int nj{j + a}; int nk{k + std::abs(nj - O)}; if (0 <= nj and nj < S and nk <= K) { next[nj][nk] += dp[j][k]; } } } } dp = std::move(next); } mint ans{}; for (int k{K % 2} ; k <= K ; k += 2) ans += dp[O][k]; std::cout << ans.val() << '\n'; }
感想
- バチャで解けず、解説AC
- 自分の考察はかなりダメダメだった。そもそも最小操作回数が回になるようなものだけ数えようとしていた。阿呆。
- 既に置いたの数に注目する定石の手法をベースに考察していたが、今回はうまくいかないようだ。
- ナップザックでよく現れる種類数がルートで抑えられるやつに似ているが、を考慮しなくて良いことが違う。
- 乗が通るらしい。面白い問題なのにもったいない。
*1:実際には両方の方向からボールが通過するような状況はありえないことに注意