扩展kmp
令 z [ i ] z[i] z[i] 代表 i i i 之后的字符串与原先字符串的最长公共前缀
r r r 为目前 g e t get get 到的最大位置,l为对应的左端点
很明显的状态转移
比如现在枚举到了 i i i 这个位置
i i i 在 [ l , r ] [l,r] [l,r] 的范围内,首先 S [ l , r ] = = S [ 1 , r − l + 1 ] S[l,r]==S[1,r-l+1] S[l,r]==S[1,r−l+1]
于是 S [ i , r ] = = S [ i − l + 1 , r − l + 1 ] S[i,r]==S[i-l+1,r-l+1] S[i,r]==S[i−l+1,r−l+1]
那么显然 z [ i ] = m i n ( z [ i − l + 1 ] , r − i + 1 ) z[i]=min(z[i-l+1],r-i+1) z[i]=min(z[i−l+1],r−i+1) 不能超过长度
假设 z [ i − l + 1 ] + ( i − l + 1 ) − 1 = r − l + 1 z[i-l+1]+(i-l+1)-1=r-l+1 z[i−l+1]+(i−l+1)−1=r−l+1,也就是 z [ i ] + i − 1 = r z[i]+i-1=r z[i]+i−1=r
但是 r + 1 r+1 r+1 处还可能匹配 因为 S r + 1 ≠ S r − l + 2 S_{r+1}\neq S_{r-l+2} Sr+1=Sr−l+2 但不一定不等于前缀的 S z [ i ] + 1 S_{z[i]+1} Sz[i]+1
然后暴力即可 由于 z [ i ] + + z[i]++ z[i]++ 的话一定会使r变大
所以是不断递增的 w h i l e while while 循环最多执行 n n n 次(执行 w h i l e while while 循环的前提是 z [ i ] + i − 1 = r z[i]+i-1=r z[i]+i−1=r )
注意 1 1 1 不能进入循环 否则一直 z [ i ] = z [ i ] z[i]=z[i] z[i]=z[i] 每个 i i i 都会执行 w h i l e while while 循环 使得算法失效
void getz()
{
z[1]=n;
int l=0,r=0;
for(int i=2;i<=n;i++){
if(i<=r)z[i]=min(z[i-l+1],r-i+1);
while(i+z[i]<=n&&b[i+z[i]]==b[z[i]+1])z[i]++;
if(i+z[i]-1>r)r=i+z[i]-1,l=i;
}
}
AC自动机
f a i l [ i ] fail[i] fail[i] 代表 i i i 这个节点的失配指针,即最长公共后缀(不能是自己)
先建立 t r i e trie trie 树,然后 b f s bfs bfs 建立自动机,注意为了防止自己匹配自己,第一层 p u s h push push 进队列即可
void insert(int i){
string x;
cin>>x;
s[i]=x;
int now=0;
for(auto j:x){
if(!tr[now][j-'a'])
tr[now][j-'a']=++cnt;
now=tr[now][j-'a'];
}
mp[i]=now;
}
void build(){
queue<int>q;
for(int i=0;i<26;i++)if(tr[0][i])q.push(tr[0][i]);
while(!q.empty()){
int now=q.front();q.pop();
for(int i=0;i<26;i++){
if(tr[now][i]){
fail[tr[now][i]]=tr[fail[now]][i];
q.push(tr[now][i]);
}
else tr[now][i]=tr[fail[now]][i];
}
}
}
回文自动机
由于以 s [ i ] s[i] s[i] 结尾的回文串必定是由以 s [ i − 1 ] s[i-1] s[i−1] 结尾的回文串转移过来
所以建立回文树
g e t f a i l ( x , i ) getfail(x,i) getfail(x,i) 代表找上一位在回文树中下标为 x x x,并且开头前一个与 s [ i ] s[i] s[i] 相等 的下标
即最长回文后缀
为什么点要插在最长回文后缀的后面 :
因为如果不是最长回文后缀 那么一定出现过 回文串中回文串的性质
0根代表偶数回文 1根代表奇数回文
l e n [ 1 ] = − 1 → len[1]=-1 \rightarrow len[1]=−1→ 保证每次+=2结果正确性
f a i l fail fail 指针的求解:
由于最长回文后缀不能是自己 所以应用 f a i l [ p o s ] fail[pos] fail[pos] 求解 而不是 p o s pos pos(因为会直接返回 p o s pos pos 导致 r e t u r n return return 本身),直接根据 g e t f a i l getfail getfail 函数转移即可
时间复杂度证明:
由于每次插入点都会使得深度 + 1 +1 +1,深度最多加 n n n 次,则 w h i l e while while 循环最多执行 n n n 次
ps:第二个有点问题,但是时间复杂度理论是正确的,不会证明
int getfail(int x,int i){
while(s[i-len[x]-1]!=s[i])x=fail[x];
return x;
}
void solve()
{
string s;
cin>>s;
int n=s.size(),pre=0;
s=" "+s;
len[1]=-1,fail[0]=1;
for(int i=1;i<=n;i++){
int pos=getfail(pre,i);
if(!tr[pos][s[i]-'a']){
fail[++tot]=tr[getfail(fail[pos],i)][s[i]-'a'];
tr[pos][s[i]-'a']=tot;
len[tot]=len[pos]+2;
cnt[tot]=cnt[fail[tot]]+1;
}
pre=tr[pos][s[i]-'a'];
}
}
h a l f [ i ] half[i] half[i] 表示以 i i i 结尾的最长回文后缀 且 长度小于等于 l e n [ i ] / 2 len[i]/2 len[i]/2
for(int i=1;i<=n;i++){
int pos=getfail(pre,i);
if(!tr[pos][s[i]-'a']){
fail[++tot]=tr[getfail(fail[pos],i)][s[i]-'a'];
tr[pos][s[i]-'a']=tot;
len[tot]=len[pos]+2;
if(len[tot]<=2)half[tot]=fail[tot];
else {
int tmp=half[pos];
while(s[i-1-len[tmp]]!=s[i]||2*len[tmp]+4>len[tot]){
tmp=fail[tmp];
}
half[tot]=tr[tmp][s[i]-'a'];
}
}
pre=tr[pos][s[i]-'a'];
}
后缀数组
具体详见代码
void get_sa(){
int m=130;//m是桶最大编号
vector<int>sa(n+1,0),sa2(2*n+1,0),rk(2*n+1,0),c(max(n,m)+1,0);
/*
sa[i]代表排名第i的后缀的起始编号
sa2[i]代表第二关键字排序后排名第i的后缀起始编号
rk[i]代表以i为起始后缀的排名
c[i]即为桶 用来存第一关键字的个数
*/
for(int i=1;i<=n;i++)c[rk[i]=s[i]]++;
//一开始第一关键字为s[i] 存进桶即可
for(int i=1;i<=m;i++)c[i]+=c[i-1];
//做前缀和 这样即可求出每个第一关键字的最大排名
for(int i=n;i>=1;i--)sa[c[rk[i]]--]=i;
//按照第一关键字排序
for(int k=1;k<=n;k<<=1){
int num=0;
for(int i=n-k+1;i<=n;i++)sa2[++num]=i;
//由于n-k+1~n的无第二关键字 所以排名最靠前
for(int i=1;i<=n;i++)if(sa[i]>k)sa2[++num]=sa[i]-k;
//按照sa[i]从小到大遍历 保证排名从前往后
//如果sa[i]>k 说明这个可以当作第二关键字 则存入sa2中
for(int i=1;i<=m;i++)c[i]=0;
//初始化桶
for(int i=1;i<=n;i++)c[rk[i]]++;
for(int i=1;i<=m;i++)c[i]+=c[i-1];
//存第一关键字并做前缀和
for(int i=n;i>=1;i--)sa[c[rk[sa2[i]]]--]=sa2[i];
//倒着遍历保证排序从后往前
//rk[sa2[i]]是第二关键字排名为i的后缀的第一关键字
//就可以像上面那样 由于桶前缀和已经对第一关键字排序
//所以实质上就是倒着遍历 每个第一关键字不影响 都在一个区间内
//然后倒着排序第二关键字
swap(rk,sa2);//下面要更新rk数组,所以做个临时交换
rk[sa[1]]=1;//显然排名为1的后缀的rk是1
num=1;
for(int i=2;i<=n;i++){
rk[sa[i]]=(sa2[sa[i]]==sa2[sa[i-1]]&&sa2[sa[i]+k]==sa2[sa[i-1]+k])?num:++num;
//如果第一关键字和第二关键字和上一个均相同,则排名一样,num无需++
}
if(num==n)break;
//num==n说明所有后缀排序均不相同,排序结束
m=num;//更新容器最大值
}
for(int i=1;i<=n;i++)cout<<sa[i]<<' ';
}
height数组的求解
void get_height(){
//height[i]代表LCP(sa[i],sa[i-1])
height[1]=0;
int k=0;
//k代表现在匹配到的长度
for(int i=1;i<=n;i++){
//按后缀进行遍历 很容易知道height[rk[i]]>=height[rk[i-1]]-1
int j=sa[rk[i]-1];//上一个排名的后缀起始下标
if(k)k--;//-1是因为第一个字符失去
while(s[i+k]==s[j+k])k++;//暴力匹配 k最大是n 总时间复杂度最大为O(n)
height[rk[i]]=k;//k即为最长公共前缀
}
}
后缀数组模板
struct SA{
vector<int>sa,sa2,c,rk,height;
vector<vector<int>>dp;
void init(string s,int n){
int m=150;
sa=vector<int>(max(n,m)+1,0);
c=height=sa;
sa2=rk=vector<int>(n*2+1,0);
//可能re的点
dp=vector<vector<int>>(n+1,vector<int>(31,1e9+10));
for(int i=1;i<=n;i++)c[rk[i]=s[i]]++;
for(int i=1;i<=m;i++)c[i]+=c[i-1];
for(int i=n;i>=1;i--)sa[c[rk[i]]--]=i;
for(int k=1;k<=n;k<<=1){
int num=0;
for(int i=n-k+1;i<=n;i++)sa2[++num]=i;
for(int i=1;i<=n;i++)if(sa[i]>k)sa2[++num]=sa[i]-k;
for(int i=1;i<=m;i++)c[i]=0;
for(int i=1;i<=n;i++)c[rk[i]]++;
for(int i=1;i<=m;i++)c[i]+=c[i-1];
for(int i=n;i>=1;i--)sa[c[rk[sa2[i]]]--]=sa2[i];
num=1;swap(rk,sa2);
rk[sa[1]]=1;
for(int i=2;i<=n;i++){
rk[sa[i]]=(sa2[sa[i]]==sa2[sa[i-1]]&&sa2[sa[i]+k]==sa2[sa[i-1]+k])?num:++num;
}
m=num;
if(m==n)break;
}
int k=0;
for(int i=1;i<=n;i++){
int j=sa[rk[i]-1];
if(k)k--;
while(max(i,j)+k<=n&&s[i+k]==s[j+k])k++;
height[rk[i]]=k;
}
for(int i=1;i<=n;i++)dp[i][0]=height[i];
for(int j=1;(1<<j)<=n;j++){
for(int i=1;i+(1<<j)-1<=n;i++){
dp[i][j]=min(dp[i][j-1],dp[i+(1<<j-1)][j-1]);
}
}
}
int lca(int l,int r){
l=rk[l],r=rk[r];
if(l>r)swap(l,r);
l++;
int len=lg[r-l+1];
return min(dp[l][len],dp[r-(1<<len)+1][len]);
}
};
后缀自动机模板
struct sam{
vector<vector<int>>tr;
vector<int>len,fa;
int tot=1,now=0,lst=1;
void init(int n){
tr=vector<vector<int>>(n*2+10,vector<int>(30,0));
len=vector<int>(n*2+10,0);
fa=len;
}
void insert(char c){
c-='a';
now=++tot;
len[now]=len[lst]+1;
while(!tr[lst][c]&&lst){
tr[lst][c]=now;
lst=fa[lst];
}
if(lst==0)fa[now]=1;
else {
int x=tr[lst][c];
if(len[x]==len[lst]+1){
fa[now]=x;
}
else {
int y=++tot;
tr[y]=tr[x];
fa[y]=fa[x];
len[y]=len[lst]+1;
fa[x]=fa[now]=y;
while(tr[lst][c]==x&&lst){
tr[lst][c]=y;
lst=fa[lst];
}
}
}
lst=now;
cnt[now]=1;
}
};