字符串模板笔记

字符串

循环串字典序

给定字符串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;
}
posted @ 2021-03-02 14:43  bqlp  阅读(95)  评论(0)    收藏  举报