解説とそこまで変わりませんが一応此方の解き方をば
要は「連続した文字を採用しない部分文字列は何通りありますか」
DPとしてはシンプルですね。遷移方法なんですが、まずいろいろ考えないと末尾にその文字を加えればいいので
勿論こんなことしたら下記を一切考えてないです
- 遷移先は無数にある
- 重複した文字列は考えない
といったことを考えると、実は文字種ごとに一番左からだけ選んで遷移すれば良いです。
図の通り、一番左の部分だけ取ればいいのである。
- 右の文字を取ってもできる文字は左で取った場合と重複をしてしまう
- 左の文字から取った場合でしか遷移できない文字先はあるが、右からしか遷移がカバーできない部分はない
制約の通り、隣には遷移できないのでそちらには気を付けよう。 こうすると文字列は高々26個しかないので遷移は26個しかなくで書ける
string s; cin >> s; ll n = s.size(); vector<vector<ll>> nv(26); vector<ll> ni(26); REP(i, n) nv[s[i] - 'a'].push_back(i); vector<mint> dp(n); REP(i, 26) if (!nv[i].empty())dp[nv[i][0]]++; ni[s[0] - 'a']++; mint ans = 0; REP(i, n) { ans += dp[i]; if (i < n - 1) { ni[s[i + 1] - 'a']++; } REP(v, 26) { if (ni[v] >= nv[v].size())continue; dp[nv[v][ni[v]]] += dp[i]; } } cout << ans.val(); return 0;
あと、逆に遷移を式で表すと
なので、累積和を持っておくと文字の種類がめちゃくちゃ増えても対応できる 下記は1文字目のカウント用に、2つ分の余分なスペースを確保したDP。
string s; cin >> s; ll n = s.size(); vector<vector<ll>> nv(26); vector<ll> ni(26); REP(i, n) nv[s[i] - 'a'].push_back(i); vector<mint> dp(n + 2); vector<mint> sum(n + 2); dp[0] = sum[0] = sum[1] = 1; REP(i, n) { ll v = s[i] - 'a'; dp[i + 2] = sum[i]; if (ni[v] > 0) dp[i + 2] -= sum[nv[v][ni[v] - 1]]; ni[v]++; sum[i + 2] = sum[i + 1] + dp[i + 2]; } cout << (sum[n + 1] - 1).val(); return 0;
うーん、ちゃんと書いたら「ほぼ同じ」じゃなくて「まったく同じ」だったなぁ。
累積和若干ややこしかったので終わった後組むの難しかったです。