两个不怎么常见的算法
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;
}
}