CF245H题解

我们来看一下这道题:

区间问题,容易想到区间 $dp$

那么设 $dp_{l, r}$ 表示在区间 $(l, r)$ 内有多少个回文子串。

因为两个回文串组合起来不一定是回文串,例:$aa$ 与 $bb$,

而两个非回文串组合起来有可能是回文串,例:$abc$ 与 $ba$,

所以 $dp_{i, j}$ 无法通过 $dp_{i, k}$ 与 $dp_{k + 1, j}$ 所转移。

那么我们换一个角度想:

首先,区间 $(l + 1, r)$ 里的回文串也是区间 $(l, r)$ 里的回文串,区间 $(l, r - 1)$ 同理。

$$dp_{l, r} = dp_{l + 1, r} + dp_{l, r - 1}$$

但两区间都包含区间 $(l + 1, r - 1)$,我们再减去他:

$$dp_{l, r} = dp_{l + 1, r} + dp_{l, r - 1} - dp_{l + 1, r - 1}$$

上面其实就是用了容斥原理,干嘛要说那么具体呢?

还考虑到整个区间是否为回文串

$$dp_{l, r} = dp_{l + 1, r} + dp_{l, r - 1} - dp_{l + 1, r - 1} + check (l, r) ? 1 : 0$$

$? :$ 是三位运算符,不懂可去网上找一找,

其中 $check (l, r)$ 表示区间 $(l, r)$ 是否为回文串,

在求 $check (l, r)$ 要用到记忆化搜索。

得出了 $dp_{i, j}$ 后,查询可 $O(1)$ 完成。

具体可看程序。

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;

const int maxn = 5010;
char s[maxn];
int n, f[maxn][maxn], dp[maxn][maxn];

bool check (int l, int r) {
    if (f[l][r] != -1) return f[l][r]; // 记忆化搜索
    if (l >= r) return f[l][r] = 1; // 注意判断条件!!! 
    if (s[l] != s[r]) return f[l][r] = 0;
    return f[l][r] = check (l + 1, r - 1);
}

int main() {
    memset (f, -1, sizeof (f)); // 注意!!!
    scanf ("%s", s + 1); n = strlen (s + 1);
    for (int i = 1; i <= n; ++i) dp[i][i] = 1; // 初始化
    for (int l = 2; l <= n; ++l) {
        for (int i = 1; i <= n - l + 1; ++i) {
            int j = i + l - 1;
            dp[i][j] = dp[i + 1][j] + dp[i][j - 1] - dp[i + 1][j - 1];
            if (check (i, j)) dp[i][j] ++;
        }
    } // 预处理
    int T; scanf ("%d", &T);
    while (T --) {
        int x, y; scanf ("%d%d", &x, &y);
        printf ("%d\n", dp[x][y]); // 输出
    }
    return 0;
}
posted @ 2021-09-22 23:10  wangzhongyuan  阅读(7)  评论(0编辑  收藏  举报  来源