字符串小结

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 一.最小表示法
  • 二.字符串哈希r
  • 三.AC自动机
  • 四.Manacher


前言

本篇文章对字符串的一些算法做了总结


一、最小表示法

https://www.luogu.com.cn/problem/P1368

给定一个字符串a,可以把字符串的第一个字符移动到最后,可以操作任意多次,经过n次的移动后,形成字典序最小的字符串是什么,可以明显地看出经过n次移动后所形成的字符串,就是在原有字符串的基础上选择一个位置,从该位置上输出到最后,再从头输出到该位置就是改变后的字符串,我们要找的最小表示就是去找这个位置。

如何去找?

可以设置两个指针l和r,让l的初值为0,也就是指向第一个位置,让r的初值为1,指向第二个位置同时设置一个k初值为0,依次比较a[(l+k)%n]和a[(r+k)%n],这里要取余,为了回到左边的位置否则会超过字符串长度,如果一样就让k++,若前面那个大就让l指向l+k+1的位置,否则就让让r指向r+k+1的位置,最后返回min(l,r),注意每次改变l和r的位置后都要让k=0。

总得来说就是不断更新l和r的位置,最后找到一个最小表示的开始地方,复杂度为O(n).

代码如下;

int zx(string a)
{
	 int l=0,r=1,k=0;
	 int n=a.length();
	 while(l<n&&r<n&&k<n)
	 {
	 	if(a[(l+k)%n]==a[(r+k)%n])k++;
	 	else 
	 	{
	 		if(a[(l+k)%n]>a[(r+k)%n])l+=k+1;
	 		else r+=k+1;
	        if(r==l)r++;
	        k=0;
		}
	 }
	 return min(l,r);//返回最小最小表示的开始位置
}

二、字符串哈希

所谓的字符串哈希,就是让每个不同的字符串对应一个固定的数字,至于如何实现,可以设置一个base和取余数mod,这里的base和mod最好用质数,这样可以让冲突最小,h数组表示一个字符串的前缀哈希值,用p数组表示base的幂次值,h数组可以递推来求,h[i]=h[i-1]*base+str[i]-'a'+1,在求每一端的哈希值时要注意,根据前缀和的求法,本来直接让h[r]-h[l-1]即可,但是h[r]和h[l-1]在前面的数的幂次是不一样的所以h[l-1]需要再乘上base的r-l+1次才可以,因为h[r]的最高幂次是r-1而h[l-1]的最高幂次是l-2。

这里用了unsigned long long 的自然溢出,否则会导致数值太大溢出

代码如下

typedef unsigned long long ull;
const int maxn=1e6+7;
const int seed=131;
char str[maxn];
ull h[maxn],p[maxn];//h数组是求前缀哈希值,p数组是求进制的i次幂
//求一段区间的哈希值
ull get(int l,int r){	return h[r]-h[l-1]*p[r-l+1];	}

int main(){
    scanf("%s",str+1);
    int n=strlen(str+1);
    p[0]=1;
    for(int i=1;i<=n;i++){
        h[i]=h[i-1]*seed+str[i]-'a'+1;
//注意:“+1”不能省,否则“agh”与“gh”表示用一个数,也可以写h[i]=h[i-1]*seed+str[i];  
        p[i]=p[i-1]*seed;
	    }
}

三.AC自动机

 https://www.luogu.com.cn/problem/P3808
上面是AC自动机的模板题

AC自动机感觉就是kmp和trie数的结合体,在字典树上的每一个节点都有一个fail值,我们称它为失配指针,具体的某个结点的fail值指的以这个结点为结尾的后缀和以fail值为结尾的字串(包括fail这个点)有最大公共部分,至于如何实现fail指针的创建,我们可以用BFS去实现,因为每个结点的fail总是指向比它深度更浅的结点,那到底怎么指呢,其实就是指向它父亲结点的fail的相同儿子处,感觉其实是一个递归的过程。

在这道题中,我们创建好trie树,并设置好fail指针,对模式串进行匹配时,要对每一个模式串的字符跳fail值,直到没有fail可跳后再去到下一个字符,就这样依次遍历每一个字符,对于每一个字符都去统计一下看出现了几个字串,记住每次统计好一个串后要标记一下,防止该字串在模式串中多次出现。当然有时候这样跳也会TLE,所以有时候我们记一下初始的位置,最后再统一走fail就好了。

代码如下:

int id,n;
struct tree{
	int vis[26];
	int fail;
	int cnt;
}trie[maxn];
void build(string a)
{
	int k=0,len=a.size();
	for(int i=0;i<len;i++)
	{
	   int x=a[i]-'a';
          if(trie[k].vis[x]==0)trie[k].vis[x]=++id;
	   k=trie[k].vis[x];		
	}
	trie[k].cnt++;
}
void get_fail()
{
	queue<int>q;
	for(int i=0;i<26;i++)
	{
		if(trie[0].vis[i]!=0)
		{
			trie[trie[0].vis[i]].fail=0;
			q.push(trie[0].vis[i]);
		}
	}
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		for(int i=0;i<26;i++)
		{
			if(trie[u].vis[i]!=0)
			{
				trie[trie[u].vis[i]].fail=trie[trie[u].fail].vis[i];//定义fail的值
				q.push(trie[u].vis[i]);
			}
			else trie[u].vis[i]=trie[trie[u].fail].vis[i];
		}
	}
}
int query(string a)
{
	int ans=0,len=a.length();
	int now=0;
	for(int i=0;i<len;i++)
	{
		now=trie[now].vis[a[i]-'a'];
		for(int t=now;t&&trie[t].cnt!=-1;t=trie[t].fail)
		{
			ans+=trie[t].cnt;
			trie[t].cnt=-1;
		}
	}
	return ans;
}

四.Manacher

马拉车算法的最大优势就是可以在O(n)的复杂度内求出最大的回文串长度

我们要先对字符串进行一个预处理,在每个字符的两边加上一个相同的符号,同时在开头和结尾加上一个都没有出现过的字符作为结束字符,这样可以让不管是奇数还是偶数的回文串都被处理出来。

我们先设置一个id用来表示当前能够达到的最右边回文串的中心,mx表示以id为中心的回文串最右边的位置,len[i]数组表示i位置的回文串半径,可以发现len[i]-1就是原来字符串的回文串长度,那我们只要求出所有的len数组即可

依次去遍历每一个字符去求len,当前位置为i若i<mx说明在id的左边有另一个和str[i]匹配的字符此时len[i]的长度至少是min(mx-i,len[2*id-i]),2*id-i就是i关于id的对应位置,但是大于mx位置的字符我们还不知道,所以这里要暴力去匹配,若i>mx,则len[i]就只能先是1,再去暴力匹配,更新len[i]的值,最后不要忘记更新id和mx

代码如下:

char   s[11000007];
char str[25000007];
int  len[25000005];
int initstr(char *s)
{
	int k=0,len=strlen(s);
	str[k++]='@';
	for(int i=0;i<len;i++){
		str[k++]='#';
		str[k++]=s[i];
	}
	str[k++]='#';
	str[k]='\0';
	return k;
}
int manacher(int n)
{
	int mx=0,id=0,maxx=0;
	for(int i=1;i<n;i++)
	{
		if(mx>i)
		    len[i]=min(mx-i,len[2*id-i]);
		else 
		    len[i]=1;
		while(str[i+len[i]]==str[i-len[i]])
		    len[i]++;
		if(len[i]+i>mx)
		{
			mx=i+len[i];
			id=i;
			maxx=max(maxx,len[i]);
		}
	}
	return maxx-1;
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值