【算法题解】回文子序列计数
题目
思路
我们这样想,我们需要求以某个位置i
为中心构成的回文串有多少种可能,那么我们可以将这个字符串S以x
作为分割点,将整个字符串一分为三分,第一份是1 ~ i-1
中某个删除方案中剩下的子序列构成的字符串L
,第二份就是i
,第三份就是i+1 ~ n
中某个删除方案下剩下的子序列构成的字符串R
。现在我们的问题就转变成了有多少种方案可以使L i R
形成一个回文串,并且L
和R
两个字符串的长度相等。
下面我们利用闫氏DP大法:
状态表示:f[i][j]
表示1 ~ i
形成的部分和j ~ n
形成的部分构成的回文子序列数,注意这里i < j
,1 ~ i
中保留的字符数目和j ~ n
中保留的字符数目相等。
状态计算:
f[i][j] = f[i][j + 1] + f[i - 1][j] - f[i-1][j+1]
- 有
i
无j
:f[i][j + 1]
- 有
j
无i
:f[i - 1][j]
为啥这里要减去f[i-1][j+1]
呢,因为用到了容斥原理,f[i][j+1] + f[i-1][j]
多算了f[i-1][j+1]
这部分。为什么呢?
假设不减会是什么情况
f[i][j + 1] = f[i][j + 2] + f[i - 1][j + 1]
f[i - 1][j] = f[i - 1][j + 1] + f[i - 2][j]
两个都包含f[i - 1][j + 1]
,那就相当于加了两次,所以我们要减去。
上面的情况都是不同时包含i 和 j
的,也就是说i 和 j
同时存在时还没有做出任何贡献
-
s[i] != s[j]
:这就不需要计算了,因为同时加上s[i]、s[j]
就构不成回文串了 -
s[i] == s[j]
:f[i][j] += f[i - 1][j + 1] + 1
为什么这样写呢:此时s[i] == s[j]
,那么s[i]s[j]
肯定就构成了一个回文串,我们就把答案+1
我们再严谨的思考一下,对于区间1 ~ i-1
和j+1 ~ n
构成的回文子序列是不是加上s[i] s[j]
后也是一个新的回文子序列呢,因为它俩相等,答案是一定的,所以还需要把答案再加上f[i-1][j+1]
。
我们计算完f[i][j]
之后,怎么算x[i]
呢,很简单,根据实际含义来看,x[i] = f[i - 1][i + 1]
最后处理一下结果即可,别忘了取模。
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10, inf = 0x3f3f3f3f, mod = 1e9 + 7;
long long f[3010][3010];
char s[3030];
void solve()
{
scanf("%s", s + 1);
int n = strlen(s + 1);
for (int i = 1; i <= n; i++)
{
for (int j = n; j > i; j--)
{
f[i][j] = f[i - 1][j] + f[i][j + 1] - f[i - 1][j + 1];
if (s[i] == s[j])
f[i][j] += f[i - 1][j + 1] + 1;
f[i][j] %= mod;
}
}
long long ans = 0;
for (int i = 1; i <= n; i++)
{
ans ^= (i * (f[i - 1][i + 1] + 1) % mod + mod) % mod;
}
cout << ans;
}
signed main()
{
#ifdef Xin
freopen("in.in", "r", stdin);
freopen("out.out", "w", stdout);
#endif
int T = 1;
while (T--)
solve();
return 0;
}