字符串模板笔记
字符串
循环串字典序
给定字符串s,可进行左移操作,求最小(大)字典序及其最小操作数(或有几个最小字典序,给出所有左移操作数等)。也可用来进行循环串计数。
char s[maxn];
int nex[maxn];
int cal_minlex(){
int i=0, j=1, k=0;
int si=strlen(s);
while(i<si && j<si && k<si){//k<si?
int t=s[(i+k)%si]-s[(j+k)%si];
if(t==0) k++;
else{
if(t<0) j=j+k+1;
else i=i+k+1;
if(i==j) j++;
k=0;
}
}
return min(i, j);//要前面一个
}
int cal_maxlex(){
int i=0, j=1, k=0;
int si=strlen(s);
while(i<si && j<si && k<si){
int t=s[(i+k)%si]-s[(j+k)%si];
if(t==0) k++;
else{
if(t<0) i=i+k+1;
else j=j+k+1;
if(i==j) j++;
k=0;
}
}
return min(i, j);
}
int main(){
while(scanf("%s", s)!=EOF){
int ans1=cal_minlex(), ans2=cal_maxlex();
int si=(int)strlen(s);
cal_next(s, si);//循环有循环节个数个
int cir=si-nex[si-1], cnt;
if(si%cir) cnt=1;
else cnt=si/cir;
printf("%d %d %d %d\n", ans1+1, cnt, ans2+1, cnt);
}
return 0;
}
KMP
int nex[maxn+maxm];
char s[maxn], a[maxm];
//abcabcabc->nex[si-1]=6!=3
void cal_next(char s[], int si) {
memset(nex, 0, sizeof(nex));
for(int i=1; i<si; i++) {
int j=nex[i-1];
while(j>0 && s[i]!=s[j]) j=nex[j-1];
if(s[i]==s[j]) j++;
nex[i]=j;
}
return;
}
char t[maxn+maxm];
int kmp() {
int n=strlen(s), m=strlen(a);
strcpy(t, s);
strcpy(t+n+1, a);
t[n]='0', t[n+1+m]='\0';
int si=n+1+m;
cal_next(t, si);
for(int i=n+1; i<si; i++) {
if(nex[i]==n) return i-(n-1)-(n+1);
}
return -2;
}
//scanf("%s%s", s, a); s为短
//cout<<kmp()+1<<endl;
(1)允许在s开头后结尾添加一些字符,求最少添加个数使s是t个完整的循环串(t>1):
因为循环,加在前面和加在后面一样:\(bcabcabc\)可到\(abcabcabc\)和\(bcabcabca\)
这里统一加在后面。 不论串s是不是循环的,如果想要s是一个循环串,它的最小循环节长度一定是\(c=si-nex[si-1]\)。\(t=nex[si-1]\)。
无法挽救:\(t=0\),\(ans=si\)
本是循环:\(t>0\)&&\(si\)%\(c==0\),\(ans=0\)
不完全循环:\(ans=c-si\)%\(c\)
区分:求所有不完全循环(非最短)不是i*cir+si,例如:aaabbbaaa,输出6789,而不是69。只能用第一短循环节为si-nex[si-1],第二短循环节为si-nex[nex[si-1]-1]... ...。
cal_next(s, si);
v.clear();
int j=nex[si-1];
while(j>0){
v.push_back(si-j);
j=nex[j-1];
}
v.push_back(si);
(2)求所有前后缀,即t既是s前缀,又是s后缀:
最长是s,次长是\(nex[si-1]\),然后\(i\)跳至\(nex[si-1]\),求i最长\(nex[i]\),其后缀也是\(nex[si-1]\)后缀,以此类推。
cal_next(s, si);
int i=si-1;
v.clear();
v.push_back(si);
while(i>0 && nex[i]>0){
v.push_back(nex[i]);
i=nex[i]-1;
}
for(int i=(int)v.size()-1; i>=0; i--)
printf("%d ", v[i]);
(3)统计每个前缀的出现次数:
前缀来自s且寻找s中的匹配数
若\(nex[i]>0\),则\(nex[nex[i]-1]\)一定为以\(s[i]\)结尾的次长前缀,以此类推。
memset(ans, 0, sizeof(ans));
for(int i=0; i<si; i++){
if(nex[i]<=0) continue;//没有匹配
ans[nex[i]-1]++;
}
for(int i=si-1; i>0; i--) ans[nex[i]-1]+=ans[i];//加上后缀的前缀
for(int i=0; i<=si; i++) ans[i]++;//加上自己
前缀来自s且寻找t中的匹配数
类似KMP构造s+#+t即可,只关注\(i\geq n+1\)的\(nex[i]\),且最后\(ans\)不用再加1。
(4)统计本质不同的子串数目(连续的):
下面求字符串s添加一个新字符c后,可新增的字串。令t=s+c,反转得到\(t^{\sim}\),计算\(t^{\sim}\)的\(\max(nex[i])\),即最长的出现在s中的前缀其长度,且所有更短的前缀也出现了。 因此,添加c后新增字串为\(|s|-nex_{\max}+1\),其中1为字符串t。\(O(n)\)
总复杂度\(O(n^2)\)。
同理,可以计算在头部添加一个字符,或者从尾或者头移除一个字符时的本质不同子串数目。 当然,移除的变化数目等于添加的变化数目。
多字符串匹配:
枚举前缀,O(min(si)*sum(si))
#define maxn 213
#define maxm 213
#define inf 426
int nex[maxn+maxm];
char s[maxn], a[4013][maxm];
void cal_next(char s[], int si) {
memset(nex, 0, sizeof(nex));
for(int i=1; i<si; i++) {
int j=nex[i-1];
while(j>0 && s[i]!=s[j]) j=nex[j-1];
if(s[i]==s[j]) j++;
nex[i]=j;
}
return;
}
int kmp(int st, int x) {
char t[maxn+maxm];
int n=strlen(s+st), m=strlen(a[x]);
strcpy(t, s+st);
strcpy(t+n+1, a[x]);
t[n]='0', t[n+1+m]='\0';
int si=n+1+m;
cal_next(t, si);
int res=-1;
for(int i=n+1; i<si; i++) {
res=max(res, nex[i]);
}
return res;
}
void Getsub(char s[maxm], int st, int ne, char t[maxm]){
for(int i=st; i<st+ne; i++) t[i-st]=s[i];
t[ne]='\0';
return;
}
int main(){
char an[maxm], t[maxm];
int n, ans, si;
while(scanf("%d", &n)!=EOF && n){
scanf("%s", s);
n--;
for(int i=0; i<n; i++){
scanf("%s", a[i]);
}
ans=-1;
int res=inf;
si=strlen(s);
for(int i=1; i<=si; i++){
res=inf;
for(int j=0; j<n; j++){
res=min(res, kmp(i-1, j));
}
if(res<ans) continue;
Getsub(s, i-1, res, t);
if(res>ans){
ans=res, strcpy(an, t);
}
else if(res==ans){
if(strcmp(an, t)>0) strcpy(an, t);
}
}
if(ans<=0) printf("IDENTITY LOST\n");
else{
an[ans]='\0';
printf("%s\n", an);//输出最小字典序
}
}
return 0;
}
exKMP
Z函数:\(z[i]\)为满足从位置\(i\)开始且为\(s\)前缀的字符串的最大长度。
从0开始就是\(s\)与\(s[i,n-1]\)的最长公共前缀(LCP),\(aaaaa\)是\(04321\)可以超过\(i\)的,并不是只匹配\([0,i)\),\(ababa\)为\(00301\)。
int z[maxn];
void exkmp(char s[], int si) {
for (int i=1, l=0, r=0; i<si; ++i) {
if (i<=r && z[i-l]<r-i+1){
z[i]=z[i-l];
} else {
z[i]=max(0, r-i+1);
while (i+z[i]<si && s[z[i]]==s[i+z[i]])++z[i];
}
if (i+z[i]-1>r) l=i, r=i+z[i]- 1;
}
}
Manacher
马拉车:在字符串\(s\)中寻找最长回文连续子串。
实际在\(O(n)\)找出以各个\(s[i]\)为中心的半径。
//使长度变为基数
char s_new[maxn*2];
int p[maxn*2];
int Init()
{
int len = strlen(s);
s_new[0] = '$';
s_new[1] = '#';
int j = 2;
for (int i = 0; i < len; i++){
s_new[j++] = s[i];
s_new[j++] = '#';
}
s_new[j] = '\0';//$对应\0
return j;
}
int Manacher()
{
int len = Init(), max_len = -1, id, mx = 0;
for (int i = 1; i < len; i++){
if (i < mx) p[i] = min(p[2 * id - i], mx - i);
else p[i] = 1;
while (s_new[i - p[i]] == s_new[i + p[i]]) p[i]++;
if (mx < i + p[i]) id = i, mx = i + p[i];
max_len = max(max_len, p[i] - 1);
}
return max_len;
}
//s从s[0]开始读入
AC自动机
给出\(n\)个\(s\),求\(a\)中能匹配多少种\(s\),\(O(sun(s.si)+a.si)\)。
只输出种数:
允许有相同的\(s\),res+=2
#include <bits/stdc++.h>
#define maxn 500013 //sum(s.si)
#define maxm 1000013 //max(s.si, a.si)
using namespace std;
struct AC {
int tr[maxn][26], tot;
int e[maxn], fail[maxn];
void init() {
memset(tr, 0, sizeof(tr));
memset(e, 0, sizeof(e));
memset(fail, 0, sizeof(fail));
tot=0;
}
void insert(char *s) {//add s
int u=0;
for(int i=1; s[i]; i++) {//不用传si了
if(!tr[u][s[i]-'a']) tr[u][s[i]-'a']=++tot;
u=tr[u][s[i]-'a'];
}
e[u]++;
}
queue<int> q;
void build() {//after add all s
for(int i=0; i<26; i++)
if (tr[0][i]) q.push(tr[0][i]);
while(q.size()) {
int u=q.front();
q.pop();
for(int i=0; i<26; i++) {
if(tr[u][i])
fail[tr[u][i]]=tr[fail[u]][i], q.push(tr[u][i]);
else tr[u][i]=tr[fail[u]][i];
}
}
}
int query(char *t) {
int u=0, res=0;
for(int i=1; t[i]; i++) {
u=tr[u][t[i]-'a'];//遍历所有当前后缀匹配s
for (int j=u; j && e[j]!=-1; j=fail[j]) {
res+=e[j], e[j]=-1;//设为-1,因为多次匹配算一次
}
}
return res;
}
};
AC ac;
char s[maxm];
int n;
int main() {
int T;
cin>>T;
while(T--) {
ac.init();
scanf("%d", &n);
for (int i=1; i<=n; i++) {
scanf("%s", s+1);
ac.insert(s);
}
ac.build();
scanf("%s", s+1);
printf("%d\n", ac.query(s));
}
return 0;
}
输出每种\(s\)的匹配数:
仅有注释部分有修改,该query()返回最大的匹配数
不允许有相同的\(s\),val[i]中存的以i结束的s(唯一)被匹配的次数
cnt[i]存s[i]的次数
aa被aaaaa匹配4次,而不是2次
#include <bits/stdc++.h>
#define maxn 83 //(s.si)
#define maxm 1000013 //max(s.si, a.si)
#define N 163 //cnt(s)
using namespace std;
struct AC {
int tr[maxn*N][26], tot;
int e[maxn*N], fail[maxn*N];
int val[maxn*N], cnt[maxn*N];
void init() {
memset(tr, 0, sizeof(tr));
memset(e, 0, sizeof(e));
memset(fail, 0, sizeof(fail));
memset(val, 0, sizeof(val));
memset(cnt, 0, sizeof(cnt));
tot=0;
}
void insert(char *s, int id) {//add s
int u=0;
for(int i=1; s[i]; i++) {
if (!tr[u][s[i]-'a']) tr[u][s[i]-'a']=++tot;
u=tr[u][s[i]-'a'];
}
//e[u]++;
e[u]=id;//以u结束的是第id个s(前提要没有相同的s)
}
queue<int> q;
void build() {//tr不变
for(int i=0; i<26; i++)
if(tr[0][i]) q.push(tr[0][i]);
while(q.size()) {
int u=q.front();
q.pop();
for(int i=0; i<26; i++) {
if(tr[u][i])
fail[tr[u][i]]=tr[fail[u]][i], q.push(tr[u][i]);
else tr[u][i]=tr[fail[u]][i];
}
}
}
int query(char *t) {
int u=0, res=0;
for(int i=1; t[i]; i++) {
u=tr[u][t[i]-'a'];
for(int j=u; j; j=fail[j]) val[j]++;
//遍历所有当前后缀匹配s
// for (int j=u; j && e[j]!=-1; j=fail[j]) {
// res+=e[j], e[j]=-1;
// }
}
for(int i=0; i<=tot; i++) { //遍历所有s的结尾点
if(e[i]) res=max(res, val[i]), cnt[e[i]]=val[i];
}
return res;
}
};
AC ac;
char s[N][maxn], t[maxm];
int n;
int main() {
int T;
cin>>T;
while(T--) {
ac.init();
scanf("%d", &n);
for(int i=1; i<=n; i++) {
scanf("%s", s[i]+1);
ac.insert(s[i], i);
}
ac.build();
scanf("%s", t+1);
int res=ac.query(t);
printf("%d\n", res);
}
return 0;
}

浙公网安备 33010602011771号