哈希,kmp,trie树和AC自动机

1.哈希

字符串哈希实际上就是把一个字符串转化为一个数字

比如 \(abc=1*2^2+2*2^1+3*2^0\) (也就是把 a 映射为1,b 映射为2,c 映射为3)

然后就没啥了。。。

关于自然溢出:

但在有时候,会出现两个字符串不相同但是整数相同的情况,此时我们把“进制”取为131/1331/13331...,模数一般取为 \(2^{64}-1\) ,因为 \(unsigned\) \(long\) \(long\) 可以自然溢出,所以就不用取模了。

核心代码:

1.准备工作

p[0]=1;	
for(reg int i=1;i<N;i++)
{
	p[i]=p[i-1]*P;
}
for(reg int i=1;i<=size2;i++)
{
	h[i]=h[i-1]*P+(ull)s2[i]-'a';
}

2.求l,r区间的哈希值

ull gethash(int l,int r)
{
	return h[r]-h[l-1]*mi[r-l+1];
}

2.KMP

显然,我还没有学懂

upd on:2024/7/2

这个算法主要解决的是字符串中关键字搜索。

暴力:从左到右一个一个匹配,时间复杂度为 \(O(nm)\) ,不够优秀。

KMP算法:利用已经部分匹配的有效信息,只修改模式串的指针,让模式串尽量移动到有效位置。

由于我没有图并且自己也不想画,下面的请自行脑补,见谅。

会发现在主串(i 指针)和模式串(j 指针)不匹配的情况下 \(j\) 指针要移动到 最长相同前后缀前缀 的下一个位置(假设这一位为 \(k\) )。因为前后缀都相同了,所以就不用匹配了,直接从下一个位置开始,利用了已经匹配的有效信息。

数学公式表达:\(p[0\) ~ \(k-1]==p[j-k\) ~ \(j-1]\)(p 为模式串)

定义\(next\) 数组表示当 \(i,j\) 指针失配时 \(j\) 要跳转的位置。

\(p[k]==p[j]\) 的时候,\(next[j+1]=next[j]+1\)

void getnxt(int next[],string s)
{
	int j=0;
	for(int i=2,j=0;i<=l;i++)
	{
		while(j&&s[i]!=s[j+1])
		{
			j=next[j];
		}
		if(s[i]==s[j+1])
		{
			j++;
		}
		next[i]=j;
		num[i]=num[j]+1;
	}
}
int KMP(string s,string t)
{
	int next[10], i=0, j=0;
	getnext(next,t);
	while(i<s.size()&&j<t.size())
	{
		if(j==-1||s[i]==t[j])
		{
			i++;
			j++;
		}
		else j=next[j];
	}
	if(j>=s.size()) return i-s.size();
	else return -1;
	 
}

3.trie树

差不多就是把一些字符串转化为一棵树

是一种用于快速查询某个字符串/字符前缀是否存在的数据结构。

插入/生成

//idx代表当前字符的编号,根节点为0 
//son数组一维下标是父节点的idx,二维下标是这个父节点的直接子节点的str[i]-'a'的值
//cnt数组表示以该idx结尾的字符串的个数,例如:有几个'abc'的字符串 
void insert(char str[])
{
	int p=0;
	for(int i=0;str[i];i++)
	{
		int u=str[i]-'a';
		if(!son[p][u]) son[p][u]=++idx;
		p=son[p][u];
	}
	cnt[p]++;
}

查询

int query(char str[])//查询字符串出现的次数 
{
	int p=0;
	for(int i=0;str[i];i++)
	{
		int u=str[i]-'a';
		if(!son[p][u]) return 0;
		p=son[p][u];	
	}
	return cnt[p];
} 

例如:\(sea,she,sell\) 我们可以得到下面的\(Trie\)
点我

关于trie树,第2,3两道题,推荐这篇题解 》》》( ⊙ o ⊙ )啊!

4.AC自动机

由于前面学的知识都忘了,一整个复习+回顾代码,发现很难肝,顺便修整了博客(

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5, M=1e6+5;
int n, son[N][26], cnt[N], idx, T;
char s[100], s1[M];
int fail[N];//失配指针 
void insert(char str[])
{
	int p=0;
	for(int i=0;str[i];i++)
	{
		int u=str[i]-'a';
		if(!son[p][u]) son[p][u]=++idx;
		p=son[p][u];
	}
	cnt[p]++;
}
void build()//建AC自动机
{
	queue<int> q;
	for(int i=0;i<26;i++)
	{
		if(son[0][i]) q.push(son[0][i]);
	}
	while(q.size())
	{
		int u=q.front();//用于BFS遍历字典树 
		q.pop();
		for(int i=0;i<26;i++)
		{
			int v=son[u][i];
			if(v) //存在 
			{
				fail[v]=son[fail[u]][i];//失配指针就是父节点的失配指针的同样的儿子 
				q.push(v);
			}
			else son[u][i]=son[fail[u]][i];//一种特殊处理,很像并查集里的路径压缩 
		}
	}
}
int query(char str[])
{
	int ans=0, u=0;
	for(int i=0;str[i];i++)
	{
		u=son[u][str[i]-'a'];
		for(int j=u;j&&cnt[j]!=-1;j=fail[j])
		{
			ans+=cnt[j], cnt[j]=-1;//防止重复计算 
		}
	}
	return ans;
} 
int main()
{
	cin>>T;
	while(T--)
	{
		memset(fail, 0, sizeof(fail));
		memset(son, 0, sizeof(son));
		memset(cnt, 0, sizeof(cnt));
		cin>>n;
		for(int i=0;i<n;i++)
		{
			cin>>s;
			insert(s);
		}
		cin>>s1;
		build();
		cout<<query(s1)<<endl;
	}
	return 0;
} 
posted @ 2024-09-02 20:18  zhouyiran2011  阅读(2)  评论(0编辑  收藏  举报