Typical DP Contest O - 文字列
解法
dp[i][j] := i 番目の文字まで見た時,同じ文字が連続している場所が j 箇所あるような場合の数
とする.
例えば,AABBBCDEE なら,連続している箇所は4箇所と数える.
次に追加する文字が f[i] 文字であるとする.
この f[i] 文字を今できている文字列に挿入するときは,f[i] 文字がいくつかのグループに分かれることになる.
そのグループの数を k とし,それぞれのグループを区別すると,その分割の仕方は$_{f[i] - 1}C_{k - 1}$ 通りである.(f[i] - 1箇所から k-1 箇所選んで仕切りを入れると k 個のグループに分かれる.)
次に,こうして分割した各グループをどこに挿入するかを考える.
(さっきのグループを前から 1, ..., k とナンバリングして,選んだ挿入箇所にこの順番で挿入する.)
同じ文字が連続している部分が j 箇所あるので,そのうちの l 箇所にグループを挿入するとする.この場所の選び方は$_jC_l$通りである.
すると,連続していなかった場所に挿入するのは,$_{sum[i] - l + 1}C_{k-l}$通りある.sum[i] はすでにできている文字列の長さで,1 を加えているのは(左)端に挿入する分を考慮している.
最後に,最終的にできた文字列で連続した部分は何通りあるかを考えると,これは j - l + f[i] - k 通りである.(もともと j 箇所あって,l 箇所に挿入されるが,文字 i のグループによって f[i] - k 箇所新たに連続する部分ができる.)
ソースコード
#include <bits/stdc++.h> using namespace std; using ll = long long; constexpr ll M = 1e9 + 7; int main() { vector<vector<ll>> comb(512, vector<ll>(512)); for(int i = 0; i < 512; ++i) { for(int j = 0; j <= i; ++j) { if(i == j || j == 0) { comb[i][j] = 1; } else { comb[i][j] = (comb[i - 1][j - 1] + comb[i - 1][j]) % M; } } } vector<int> x; vector<int> sum = {0}; for(int i = 0; i < 26; ++i) { int f; cin >> f; if(f == 0) { continue; } x.push_back(f); sum.push_back(f + sum.back()); } int const n = x.size(); vector<vector<ll>> dp(n + 1, vector<ll>(300)); dp[0][0] = 1; for(int i = 0; i < n; ++i) { for(int j = 0; j <= sum[i]; ++j) { for(int k = 1; k <= x[i]; ++k) { for(int l = 0; l <= min(j, k); ++l) { ll t = comb[x[i] - 1][k - 1]; t = (t * comb[j][l]) % M; t = (t * comb[sum[i] - j + 1][k - l]) % M; t = (t * dp[i][j]) % M; (dp[i + 1][j - l + x[i] - k] += t) %= M; } } } } cout << dp[n][0] << endl; }
感想
これは思いつかないなあ.覚えてしまいたい.