CS Academy Distinct Palindromes

リンク : https://csacademy.com/contest/archive/task/distinct-palindromes/statement/

問題概要

文字列が与えられる。この文字列の部分文字列であり、回文であるのは何種類か求めよ。

解説

文字列をSとし、i番目の文字をS[i]とする。dp[l][r]をS[l]=S[r]=σであるときに、必ずlとrを回文に含む[l~r]間の回文の種類数とする。(l~r)の間に文字cが二つ以上存在するとき、(l~r)間にありlから最も近い文字cをc_l番目、rから最も近い文字cをc_r番目とする。σc...cσと取ることになる。dp[l][r]+=dp[c_l][c_r]となる。またこのσのときの間に一つしか文字がないときの場合は、a~zのそれぞれの文字について(l~r)間に存在すればdp[l][r]+=1する。最後にσσのみも考慮してdp[l][r]+=1する。これですべての遷移が終わった。実装する際にa~z以外の文字を番兵にすることで実装が楽になる。 下の図が具体例となる。$を番兵とする。答えに∅を回文として含んでしまうため-1をする。
f:id:akusyounin:20190218224138p:plain
これ典型なんですか?

kmjpさんのコードを参考にしました。

string str;
LL n;
LL dp[1010][1010], r[1010][30], l[1010][30];
LL func(int L, int R) {
    if (dp[L][R] != -1)return dp[L][R];
    LL ret = 1;
    rep(i, 26)if (r[L][i]<R&&l[R][i]>L) {
        ret++;
        if (r[L][i] < l[R][i])ret += func(r[L][i], l[R][i]);
        ret %= MOD;
    }
    return dp[L][R] = ret;
}
int main() {
    cin >> str;
    str = (char)('a' + 26) + str + (char)('a' + 26);
    n = str.size();
    rep(i, n + 1)rep(j, n + 1)dp[i][j] = -1;
    rep(i, n)str[i] -= 'a';
    rep(i, 30)r[n - 1][i] = n, l[0][i] = -1;
    for (int i = n - 2; i >= 0; i--) {
        rep(j, 27) {
            if (str[i + 1] == j)r[i][j] = i + 1;
            else r[i][j] = r[i + 1][j];
        }
    }
    for (int i = 1; i<n; i++) {
        rep(j, 27) {
            if (str[i - 1] == j)l[i][j] = i - 1;
            else l[i][j] = l[i - 1][j];
        }
    }
    cout << (func(0, n - 1)-1+MOD)%MOD << endl;
    return 0;
}