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;
}