目前会持续填充一些字符串的模板,后续会对有关题目进行进一步展开。
【Hash】
一个比较万能的算法,在 Θ ( n ) \Theta(n) Θ(n)的预处理之后,就可以对两个字符串进行 Θ ( 常 数 ) \Theta(常数) Θ(常数)的比较了。
其基本思想是将一串字符串映射成一个数字,通过比较数字来比较字符串。
具体上是将每位字母映射成一个值,每加进来一个值,就把前面的值乘上一个位数。这些数用前缀和存起来,需要比较时移位相减就可以了。
举个栗子,比如你现在有一个全是小写字母的字符串,你可以将所有小写字母一一对应:a->0,b->1,c->2,d->3…z->25。然后每次从后面加入一个新字母时就将原来的值乘上26就可以了。比如
b
=
1
,
b
c
=
1
∗
26
+
2
=
28
,
b
c
d
=
1
∗
26
∗
26
+
2
∗
26
+
3
=
731
b=1,bc=1*26+2=28,bcd=1*26*26+2*26+3=731
b=1,bc=1∗26+2=28,bcd=1∗26∗26+2∗26+3=731。
显然这样做数字很容易就会超过maxlongint,所以我们可以将得到的值对于一个大质数(使取模后的值尽可能均匀分布)取模(其实不取模让值自然溢出也可以不过很容易被卡)。可以证明当字符串长度不大时,出现相同值的概率是较低的。
有时候我们会应对len<=1e6,query<=1e6的情况。为进一步降低冲突的概率,我们可以多Hash,同时对多个大质数取模,这时要注意常数因子带来的运行时间问题。
其实还有一种优化,我们可以取一个小质数基底,比如把原来的26进制换成29进制,然后取模,也可以使值均匀分布,而且不容易被毒瘤出题人卡掉。实际应用中,我一般会直接开longlong不取模,好像也没被卡过。
【模板】
给一个字符串,再有m个询问l1,r1,l2,r2问 [l1,r2] 和 [l2,r2] 的字符串是否相同。
保证Len<=1e6,n<=1e6
【code】
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int base=19;
const int maxn=1e6+100;
int n,m;
char s[maxn];
unsigned long long H[maxn],mul[maxn];
inline void read(int &x){
x=0;int fl=1;char tmp=getchar();
while(tmp<'0'||tmp>'9'){if(tmp=='-')fl=-fl;tmp=getchar();}
while(tmp>='0'&&tmp<='9')x=(x<<1)+(x<<3)+tmp-'0',tmp=getchar();
x=x*fl;
}
inline int query(int l,int r){
return H[r]-H[l-1]*mul[r-l+1];
}
int main(){
scanf("%s",s+1);n=strlen(s+1);
mul[0]=1;
for(int i=1;i<=n;i++) H[i]=H[i-1]*base+s[i]-'a'+1,mul[i]=mul[i-1]*base;
cin>>m;
for(int i=1;i<=m;i++){
static int l1,r1,l2,r2;
read(l1),read(r1),read(l2),read(r2);
if(query(l1,r1)==query(l2,r2))puts("Yes");
else puts("No");
}
return 0;
}
【manacher算法】
Θ ( n ) \Theta(n) Θ(n)的时间求以每个点为中心的最大回文串。(需要一定的想象力)
这是一个有对称中心的算法,如果出现"abba"的字符串就会变得很棘手。所以我们要预先进行预处理,将每个字母之间和两头插入一个一定不会出现在字符串中的字符。可以预见,插入后要求字符串一定为奇数长度,可以进行"马拉车"。
manacher算法也是利用了字符串中一个常用的思想——利用已知信息减少重复运算。
我们需要的有一个
经过插入字符处理的字符串,s[]。
记录当点为中心的最长回文串的长度的数组,len[]。
记录目前为止最远所到达的右边界,mx。和mr的对称中心,pos。
对于一个len[i],len[i]>=min(mx-i,len[pos2-i])是一定正确的。
如上图所示,如果由于在[pos2-mx,mx]中的字符是对称的,所以当len[j]的左端点大于mx的对称点时,len[i]=len[j]。
反之,如果j的左端点超过或碰到mx的对称点那么len就至少为mr-i且有可能向右扩展。
整个算法for循环i一遍。mx从0右移至n一遍,所以总复杂就是 Θ ( n ) \Theta(n) Θ(n)
【模板】
给定一个字符串,求出其最长回文子串。
多个测试数据,每个测试数据一行,仅由小写字母组成的字符串。
【code】
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1.1e7+1000;
char s[maxn<<1];int n;
int len[maxn<<1];
int main(){
int kase=0;
while(1){
scanf("%s",s+1);n=strlen(s+1);
if(s[1]=='E'&&s[2]=='N'&&s[3]=='D')break;
for(int i=n<<1;i>=2;i-=2)s[i]=s[i>>1],s[i-1]='*';s[0]='*';
n=(n<<1)+1,s[n]='*',s[n+1]=0;
int pos=0,mr=0,ans=0;
for(int i=1;i<=n;i++){
if(mr>1)len[i]=min(mr-i,len[(pos<<1)-i]);
else len[i]=1;
while(s[i-len[i]]==s[i+len[i]])len[i]++;
if(i+len[i]>mr){
mr=i+len[i];
pos=i;
}
ans=max(ans,len[i]-1);
}
printf("Case %d: %d\n",++kase,ans);
}
return 0;
}