manacher
马拉车通过在每个字符间插入一个特殊字符,使得字符串长度为奇数,从而保证每个字符都有中心。在每个中心记录回文串的长度。
马拉车的扩展方式和
通常马拉车的题目统计回文串需要与其他数据结构结合,如线段树,树状数组等。需要一定的基本功。
void manacher(char* str,int n) {
int ans = 0, t = 0;
for (int i = 1; i <= n; ++i) {
if (t + f[t] >= i)f[i] = min(f[t - (i - t)], t + f[t] - i);
while (i - f[i] - 1 > 0 && i + f[i] + 1 <= n && str[i - f[i] - 1] == str[i + f[i] + 1])f[i]++;
if (f[i] + i > f[t] + t)t = i;
ans = max(ans, f[i]);
}
printf("%d", ans);
}
CF:30 E. Tricky and Clever Password
回文树
回文树分为奇根和偶根,偶根的
回文树不再考虑回文中心,是以右边界为这个回文串的终点。
回文树的构建与SAM类似,找上一个回文,依次遍历
struct PAM {
int siz, tot, lst; //siz回文树大小,tot字符串处理到第几个
int cnt[MAXN], ch[MAXN][26], len[MAXN], fail[MAXN];
char s[MAXN];
PAM() { clear(); }
int node(int l) { // 建立一个新节点,长度为 l
siz++;
memset(ch[siz], 0, sizeof(ch[siz]));
len[siz] = l;
fail[siz] = cnt[siz] = 0;
return siz;
}
void clear() {
siz = -1;
lst = 0;
s[tot = 0] = '$';
node(0);
node(-1);
fail[0] = 1;
}
int getfail(int x) { // 找后缀回文
while (s[tot - len[x] - 1] ^ s[tot])x = fail[x];
return x;
}
void insert(char c) {
s[++tot] = c;
int cur = getfail(lst);
if (!ch[cur][c - 'a']) {
int x = node(len[cur] + 2);
fail[x] = ch[getfail(fail[cur])][c - 'a']; //没有儿子默认指向偶根,偶根一直指向奇根
ch[cur][c - 'a'] = x;
}
lst = ch[cur][c - 'a'];
cnt[lst]++;
}
};
最小回文划分
考虑
考虑回文串
优化
-
代表 和 所代表的回文串长度差。 -
代表这个 等差数列的最前面的位置。即第一个 -
是这个 等差数列的长度对应的 最小值。
复杂度
for (int i = 1; i <= n; ++i) {
p.insert(s[i]);
for (int x = p.lst; x > 1; x = p.slink[x]) {
g[x] = dp[i - p.len[p.slink[x]] - p.dif[x]];
if (p.dif[x] == p.dif[p.fail[x]])g[x] = min(g[x], g[p.fail[x]]);
dp[i] = min(dp[i], g[x] + 1);
}
}
双倍回文
寻找最长的
- 维护一个
小于等于当前节点长度一半的最长回文后缀,和 求法类似。即可。
前端插入字符
前端加入我们只需要找到最长回文前缀对应节点,由于每个节点对应的都是一个回文串,
例题:
HDU:5421 Victor and String
struct PAM {
int siz, totl, totr, lstr, lstl; //siz回文树大小,tot字符串处理到第几个
int cnt[MAXN], ch[MAXN][26], len[MAXN], fail[MAXN], dep[MAXN];
ll ans;
char s[MAXN << 1];
PAM() { clear(); }
int node(int l) { // 建立一个新节点,长度为 l
siz++;
memset(ch[siz], 0, sizeof(ch[siz]));
len[siz] = l;
fail[siz] = cnt[siz] = 0;
return siz;
}
void clear() {
siz = -1;
lstr = lstl = 0;
// s[] = '$';
memset(s, 0, sizeof(s));
memset(len, 0, sizeof(len));
totl = n;
totr = n - 1;
node(0);
node(-1);
fail[0] = 1;
ans = 0;
}
int getfailr(int x) { // 找后缀回文
while (s[totr - len[x] - 1] ^ s[totr])x = fail[x];
return x;
}
int getfaill(int x) { // 找后缀回文
while (s[totl + len[x] + 1] ^ s[totl])x = fail[x];
return x;
}
void push_back(char c) {
s[++totr] = c;
int cur = getfailr(lstr);
if (!ch[cur][c - 'a']) {
int x = node(len[cur] + 2);
fail[x] = ch[getfailr(fail[cur])][c - 'a']; //没有儿子默认指向偶根,偶根一直指向奇根
ch[cur][c - 'a'] = x;
dep[x] = dep[fail[x]] + 1;
}
lstr = ch[cur][c - 'a'];
if (len[lstr] == totr - totl + 1)lstl = lstr;
ans += dep[lstr];
}
void push_front(char c) {
s[--totl] = c;
int cur = getfaill(lstl);
if (!ch[cur][c - 'a']) {
int x = node(len[cur] + 2);
fail[x] = ch[getfaill(fail[cur])][c - 'a'];
ch[cur][c - 'a'] = x;
dep[x] = dep[fail[x]] + 1;
}
lstl = ch[cur][c - 'a'];
if (len[lstl] == totr - totl + 1)lstr = lstl;
ans += dep[lstl];
}
};
例题:
luogu: P5496 【模板】回文自动机(PAM)
其他方式处理回文
哈希、SA、SAM都可以用,思路都是把原串翻转。求这部分和反转后的这部分是否一致。
哈希可以快速判断某子串是否是回文串。SA和SAM考虑两个串匹配的长度和对应的位置差距可以判断是否为回文串。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】