两个不怎么常见的算法
1.Z函数
定义:z[i]表示以i开头的后缀与整个串最长的公共串长度
求法:假设i以前的字符串已经求完,记录l,r为右端最大的区间 [i,i+z[i]-1]
若 i<r ,则根据定义,z[i]=z[i-l]
但是i-l>r的情况下不能保证正确性,需要暴力判断
否则就暴力求出z函数值
用处(其实kmp都可以实现,只不过有些用z函数更直观)
1.匹配字符串
对于两个字符串跑z函数的过程,若z函数值等于模式串的长度就有一次成功匹配
其实可以将两个串粘一起(模式串+其他字符+文本串)然后跑一遍就够了
s=' '+s2+'&'+s1+'*'; for(int i=2;i<=len;i++){ if(i<=r) z[i]=min(z[i-l+1],r-i+1); while(i+z[i]<len&&s[i+z[i]]==s[z[i]+1]) z[i]++; if(i+z[i]-1>r) l=i,r=i+z[i]-1; }
2.找出循环节
根据z函数的性质,对于每一个前缀(1~i),在字符串中出现次数为
那么找到字符串长度的倍数,再判断i+z[i+1]是否为字符串长度就可以了
例题
发现有一个为 (AB)^i 的式子,就可以用z函数来处理
每次枚举AB的长度,然后奇偶分析
若i为奇数,那么C中出现奇数字符个数恒等于C最短的时候
若i为偶数,那么C中出现奇数字符的个数恒等于原串的个数
预处理出来每个位置前缀答案和后缀答案,用树状数组维护一下就行了
#include<bits/stdc++.h> #define int long long using namespace std; struct trsz{ int w[35]; void init(){ memset(w,0,sizeof(w)); } int lowbit(int x){ return x&(-x); } void insert(int id){ id++; while(id<=30) w[id]++,id+=lowbit(id); } int query(int id){ int ans=0; id++; while(id) ans+=w[id],id-=lowbit(id); return ans; } }; trsz ty; char s[1100100],ch; int t; int len,cnt[30],pre[1100100],now,sub[1100100]; int z[1100100],l,r; long long ans; signed main(){ scanf("%lld",&t); ch=getchar(); while(t--){ ch=getchar(); len=0; while(ch>='a'&&ch<='z') s[++len]=ch,ch=getchar(); memset(cnt,0,sizeof(cnt)); now=0; for(int i=1;i<=len;i++){ cnt[s[i]-'a']++; now+=((cnt[s[i]-'a']&1)?1:-1); pre[i]=now; } memset(cnt,0,sizeof(cnt)); now=0; for(int i=len;i>=1;i--){ cnt[s[i]-'a']++; now+=((cnt[s[i]-'a']&1)?1:-1); sub[i]=now; } l=r=0; z[1]=len; for(int i=2;i<=len;i++){ if(i<=r) z[i]=min(z[i-l+1],r-i+1); while(i+z[i]<=len&&s[i+z[i]]==s[z[i]+1]) z[i]++; if(i+z[i]-1>r) l=i,r=i+z[i]-1; } ans=0; ty.insert(pre[1]); for(int i=2;i<len;i++){ int gs=z[i+1]/i+1; int c_beg=i*gs+1; while(c_beg>len) c_beg-=i,gs--; if(!(gs&1)) c_beg-=i; ans+=(ty.query(sub[c_beg])*((gs+1)/2)+ty.query(sub[1])*(gs/2)); ty.insert(pre[i]); } printf("%lld\n",ans); for(int i=0;i<=len;i++) sub[i]=pre[i]=z[i]=0; ty.init(); } return 0; }
2.manacher
处理回文字符串最简单也是最优的方法
求法:记录一个数组d[i]表示长度为奇数的回文串长度的半径
先考虑求出d
假设i以前的字符串已经求完,记录l,r为右端最大的区间 [i-d[i]+1,i+d[i]-1]
若i<r,d[i]=d[l+r-i],若i+d[i]>r,超出部分用暴力
否则直接暴力
那么还有偶数长度的回文串怎么办?
将字符串中间插入无关字符(#s#t#y#h#)像这样
d[i]-1为回文串长度,#为中心匹配出来的就是偶数长度了
复杂度分析:z函数与manacher一样,都是将r从1暴力推向n的过程,故复杂度为O(n)
for(int i=1;i<=a;i++){ if(r<=i){ l=r=i; while(s[l-1]==s[r+1]) l--,r++; d[i]=r-i+1; }else{ int ii=l+r-i; d[i]=min(d[ii],r-i+1); while(s[i+d[i]]==s[i-d[i]]) d[i]++; if(i+d[i]-1>r) r=i+d[i]-1,l=i-d[i]+1; } }