回文子串问题
本文整理一些与字符串回文相关的问题。
判断子串回文
给定一个字符串s,然后有多组询问,每次给出区间[l,r],问子串s[a..b]是否为回文串。
由于询问有多次,可以考虑预处理出所有子串是否为回文,然后依次回答询问即可。有两种做法:区间dp和哈希,下面分别介绍。
区间dp做法
区间dp一般是按区间长度由短往长递推,由于回文分奇回文和偶回文,对应边界情况分别是长度为1和2的子串,预处理的时间复杂度为O(n^2)。
#include <bits/stdc++.h>
using namespace std;
char s[10005];
int dp[10005][10005], l, q, a, b;
int main() {
cin >> s; l = strlen(s);
for (int i = 0; i < l; i++)
dp[i][i] = 1;
for (int i = 1; i < l; i++)
dp[i-1][i] = s[i-1] == s[i];
for (int k = 2; k < l; k++) {
for (int i = 0; i < l; i++) {
int j = i+k;
if (j >= l) break;
if (s[i] != s[j]) dp[i][j] = 0;
else dp[i][j] = dp[i+1][j-1];
}
}
cin >> q;
for (int i = 0; i < q; i++) {
cin >> a >> b;
cout << (dp[a-1][b-1] ? "Yes" : "No") << endl;
}
return 0;
}
哈希做法
分别采用左乘和右乘的方式计算哈希,如果是回文串,哈希值必定相同,非回文串的哈希值一般不相等。由于右乘方式要计算子区间哈希值时要用到除法,而取余操作不能直接除,故而先打表求幂时,顺带把它关于模的乘法逆元也算出来,预处理的时间复杂度为O(nlogn)。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
char s[10005];
int p = 131, q, a, b, l, M = 1e9+7;
LL L[10005], R[10005], H[10005], V[10005];
LL LH(int x, int y) {
LL h = (L[y] - L[x-1] * H[y-x+1]) % M;
if (h < 0) h += M;
return h;
}
LL RH(int x, int y) {
LL h = (R[y] - R[x-1]) * V[x] % M;
if (h < 0) h += M;
return h;
}
void exgcd(LL u, LL &x, LL v, LL &y) {
if (v == 0) {
x = 1; y = 0;
} else {
exgcd(v, y, u%v, x);
y -= u/v*x;
}
}
LL inv(LL x, LL y) {
LL xx, yy;
exgcd(x, xx, y, yy);
if (xx < 0) xx += y;
return xx;
}
void init() {
H[0] = 1;
for (int i = 1; i <= l; i++) {
H[i] = H[i-1] * p % M;
V[i] = inv(H[i], M);
}
}
int main() {
cin >> (s+1); l = strlen(s+1);
init();
for (int i = 1; i <= l; i++) {
L[i] = (L[i-1] * p + s[i]) % M;
R[i] = (R[i-1] + H[i] * s[i]) % M;
}
cin >> q;
for (int i = 0; i < q; i++) {
cin >> a >> b;
cout << (LH(a,b) == RH(a,b) ? "Yes" : "No") << endl;
}
return 0;
}
51nod1092 回文字符串
题面:给出一个长度不超过1000的字符串s,问最少要插入多少个字符可使其变成回文串?
分析:记原串为s,其逆序为s1,求出s与s1的最长公共子序列lcs(s,s1)的长度len(lcs),那么答案就是len(s)-len(lcs)。
51nod1154 回文串划分
题面:给出一个长度不超过5000的字符串s,现要对其进行划分,使划分出的每个子串都是回文串,问最少可划分为几段?
分析:设dp[i]表示前i个字符可划分的最少段数,在求dp[i+1]时,依次枚举以s[i+1]结尾的子串,长度从1递增,检查子串是否为回文,并更新dp[i+1]。
#include <bits/stdc++.h>
using namespace std;
char s[5005]; int dp[5005];
int ok(int x, int y) {
while (x < y) {
if (s[x] != s[y]) return 0;
x++, y--;
}
return 1;
}
int main() {
cin >> (s+1); int l = strlen(s+1);
for (int i = 1; i <= l; i++) {
dp[i] = 1 + dp[i-1];
for (int j = i-1; j >= 1; j--)
if (ok(j,i)) dp[i] = min(dp[i], 1+dp[j-1]);
}
cout << dp[l] << endl;
return 0;
}