回文树是两棵树, 分别存储奇回文和偶回文.
显然一个字符串本质不同的回文个数是不超过$n$, 回文树的每个节点都表示一个本质不同的回文串. 节点$x$向节点$y$连一条有向边$c$表示$y$的回文串可以由$x$的回文串两端添加字符$c$得到. 每个节点设fail指针, 指向该节点表示的回文串的第二长的回文后缀所在节点.
奇回文根为$1$号节点, 偶回文根为$0$号节点, 要初始化$s[0]$为一个特殊字符.
$fail[0]$要指向奇回文的根, $fail[1]$在$getfail$时不会一定被访问到, 设成0即可.
练习1 hdu3068 最长回文
给出字符串, 求最长回文长度.
#include <iostream>
#include <string.h>
#define PER(i,a,n) for(int i=n;i>=a;--i)
#define REP(i,a,n) for(int i=a;i<=n;++i)
using namespace std;
typedef long long ll;
const int N = 1e6+10;
int n, tot, last, fail[N], len[N], ch[N][26];
char s[N];
int getfail(int x, int n) {
while (s[n-len[x]-1]!=s[n]) x=fail[x];
return x;
}
int main() {
while (~scanf("%s", s+1)) {
s[0]='#',fail[0]=1,last=0;
len[0]=0,len[1]=-1,tot=1;
n = strlen(s+1);
REP(i,1,n) {
s[i] -= 'a';
int p = getfail(last,i);
if (!ch[p][s[i]]) {
len[++tot] = len[p]+2;
fail[tot]=ch[getfail(fail[p],i)][s[i]];
ch[p][s[i]]=tot;
}
last=ch[p][s[i]];
}
int ans = 0;
REP(i,0,tot) {
memset(ch[i],0,sizeof ch[0]);
ans = max(ans, len[i]);
fail[i]=len[i]=0;
}
printf("%d\n", ans);
}
}
练习2 hdu 4513 吉哥系列故事——完美队形II
求最长的左侧非降, 右侧非增的回文串.
#include <iostream>
#include <map>
#include <string.h>
#define PER(i,a,n) for(int i=n;i>=a;--i)
#define REP(i,a,n) for(int i=a;i<=n;++i)
using namespace std;
typedef long long ll;
const int N = 1e5+10;
int n, tot, last, a[N];
int fail[N], len[N];
map<int,int> ch[N];
int val[N];
int getfail(int x, int n) {
while (a[n-len[x]-1]!=a[n]) x=fail[x];
return x;
}
int main() {
int t;
scanf("%d", &t);
while (t--) {
scanf("%d", &n);
REP(i,1,n) scanf("%d", a+i);
a[0]=-1,fail[0]=1,last=0;
len[0]=0,len[1]=-1,tot=1;
REP(i,1,n) {
int p = getfail(last,i);
if (!ch[p].count(a[i])) {
len[++tot] = len[p]+2;
fail[tot]=ch[getfail(fail[p],i)][a[i]];
ch[p][a[i]]=tot;
if (len[tot]<=2) val[tot]=len[tot];
else if (a[i]<=a[i-1]) val[tot]=val[p]+2;
else val[tot]=-1e9;
}
last=ch[p][a[i]];
}
int ans = 0;
REP(i,0,tot) {
ch[i].clear();
ans = max(ans, val[i]);
val[i]=fail[i]=len[i]=0;
}
printf("%d\n", ans);
}
}
练习3 hdu 3948 The Number of Palindromes
求本质不同的回文串个数.
#include <iostream>
#include <string.h>
#define PER(i,a,n) for(int i=n;i>=a;--i)
#define REP(i,a,n) for(int i=a;i<=n;++i)
using namespace std;
typedef long long ll;
const int N = 1e6+10;
int n, tot, last, fail[N], len[N], ch[N][26];
char s[N];
int getfail(int x, int n) {
while (s[n-len[x]-1]!=s[n]) x=fail[x];
return x;
}
int main() {
int t;
scanf("%d", &t);
REP(cas,1,t) {
scanf("%s", s+1);
s[0]='#',fail[0]=1,last=0;
len[0]=0,len[1]=-1,tot=1;
n = strlen(s+1);
REP(i,1,n) {
s[i] -= 'a';
int p = getfail(last,i);
if (!ch[p][s[i]]) {
len[++tot] = len[p]+2;
fail[tot]=ch[getfail(fail[p],i)][s[i]];
ch[p][s[i]]=tot;
}
last=ch[p][s[i]];
}
REP(i,0,tot) {
memset(ch[i],0,sizeof ch[0]);
fail[i]=len[i]=0;
}
printf("Case #%d: %d\n", cas, tot-1);
}
}
练习4 ZOJ 3661 Palindromic Substring
给定字符串, 每个字符有一个价值范围[0,25], 每个回文子串的价值为前一半所表示的26进制数, 求第$k$大价值.
回文树预处理出所有本质不同的回文的价值, 然后排序即可, 要注意回文子串的总数爆int
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string.h>
#define PER(i,a,n) for(int i=n;i>=a;--i)
#define REP(i,a,n) for(int i=a;i<=n;++i)
using namespace std;
typedef long long ll;
const int N = 1e5+10;
int n, m, tot, last, fac[N];
int fail[N], len[N], ch[N][26], val[25][N];
int rk[25][26], cnt[N];
ll k[25];
char s[N];
struct _ {int v,cnt;} a[N];
int getfail(int x, int n) {
while (s[n-len[x]-1]!=s[n]) x=fail[x];
return x;
}
int main() {
fac[0]=1;
REP(i,1,N-1) fac[i]=fac[i-1]*26ll%777777777;
int t;
scanf("%d", &t);
REP(cas,1,t) {
scanf("%d%d%s", &n, &m, s+1);
s[0]='#',fail[0]=1,last=0;
len[0]=0,len[1]=-1,tot=1;
n = strlen(s+1);
REP(i,1,m) {
scanf("%lld", k+i);
REP(j,0,25) scanf("%d", rk[i]+j);
}
REP(i,1,n) {
s[i] -= 'a';
int p = getfail(last,i);
if (!ch[p][s[i]]) {
len[++tot] = len[p]+2;
fail[tot]=ch[getfail(fail[p],i)][s[i]];
ch[p][s[i]]=tot;
REP(j,1,m) {
val[j][tot]=(val[j][p]+(ll)rk[j][s[i]]*fac[(len[tot]+1)/2-1])%777777777;
}
}
++cnt[last=ch[p][s[i]]];
}
PER(i,1,tot) cnt[fail[i]]+=cnt[i];
REP(i,1,m) {
REP(j,1,tot-1) a[j].v=val[i][j+1],a[j].cnt=cnt[j+1];
sort(a+1,a+tot,[](_ a,_ b){return a.v<b.v;});
REP(j,1,tot-1) {
if (k[i]<=a[j].cnt) {
printf("%d\n", a[j].v);
break;
}
k[i] -= a[j].cnt;
}
}
REP(i,0,tot) {
memset(ch[i],0,sizeof ch[0]);
REP(j,1,m) val[j][i]=0;
fail[i]=len[i]=cnt[i]=0;
}
puts("");
}
}
练习5 Luogu P3649 APIO2014 回文串
给你一个由小写拉丁字母组成的字符串 s。我们定义 s 的一个子串的存在值为这个子串在 s 中出现的次数乘以这个子串的长度。对于给你的这个字符串 s,求所有回文子串中的最大存在值。
建出回文树后在fail树上统计一下结果即可.
#include <iostream>
#include <string.h>
#define PER(i,a,n) for(int i=n;i>=a;--i)
#define REP(i,a,n) for(int i=a;i<=n;++i)
using namespace std;
typedef long long ll;
const int N = 1e6+10;
int n, tot, last, fail[N], len[N];
int cnt[N], ch[N][26];
char s[N];
int getfail(int x, int n) {
while (s[n-len[x]-1]!=s[n]) x=fail[x];
return x;
}
int main() {
scanf("%s", s+1);
s[0]='#',fail[0]=1,last=0;
len[0]=0,len[1]=-1,tot=1;
n = strlen(s+1);
REP(i,1,n) {
s[i] -= 'a';
int p = getfail(last,i);
if (!ch[p][s[i]]) {
len[++tot] = len[p]+2;
fail[tot]=ch[getfail(fail[p],i)][s[i]];
ch[p][s[i]]=tot;
}
++cnt[last=ch[p][s[i]]];
}
ll ans = 0;
PER(i,1,tot) cnt[fail[i]]+=cnt[i],ans=max(ans,(ll)cnt[i]*len[i]);
printf("%lld\n", ans);
}
练习6 hdu 5157 Harry and magic string
给定字符串, 求所有不相交的回文串对数.
回文树统计出每个前后缀的回文串个数.
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string.h>
#define PER(i,a,n) for(int i=n;i>=a;--i)
#define REP(i,a,n) for(int i=a;i<=n;++i)
using namespace std;
typedef long long ll;
const int N = 1e5+10;
int n, m, tot, cnt, last, fac[N];
int fail[N], len[N], ch[N][26], num[N];
ll sum[N];
char s[N], ss[N];
int getfail(int x) {
while (ss[cnt-len[x]-1]!=ss[cnt]) x=fail[x];
return x;
}
int insert(int c) {
ss[++cnt] = c;
int p = getfail(last);
if (!ch[p][c]) {
len[++tot] = len[p]+2;
fail[tot]=ch[getfail(fail[p])][c];
ch[p][c]=tot;
num[tot]=num[fail[tot]]+1;
}
last = ch[p][c];
return num[last];
}
void clear() {
REP(i,0,tot) {
memset(ch[i],0,sizeof ch[0]);
num[i]=len[i]=fail[i]=0;
}
ss[0]='#',fail[0]=1,last=0;
len[0]=0,len[1]=-1,tot=1,cnt=0;
}
int main() {
while (~scanf("%s", s+1)) {
n = strlen(s+1);
clear();
REP(i,1,n) {
s[i]-='a';
sum[i] = sum[i-1]+insert(s[i]);
}
clear();
ll ans = 0;
PER(i,1,n) ans += (ll)sum[i-1]*insert(s[i]);
printf("%lld\n", ans);
}
}