字符串hash
hash函数:BKERHash、APHash、DJBHash、JSHash等,一般用BKERHash
例题 hdu 2648
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=10005; const int INF=0x3fffffff; typedef long long LL; //字符串hash的应用 int n,m; struct node{ char name[40]; int price; }; vector<node> q[maxn]; //用于解决冲突 unsigned int BKDRHash(char *str){ //hash函数 unsigned int seed=31,key=0; while(*str){ key=key*seed+(*str++); } return key&0x7fffffff; } int main(){ int p[maxn]; char s[35]; node t; int mom,key,x,rank,len; while(cin>>n){ for(int i=0;i<maxn;i++) q[i].clear(); for(int i=0;i<n;i++){ cin>>t.name; key=BKDRHash(t.name)%maxn; //记得取余 q[key].push_back(t); } cin>>m; while(m--){ rank=0;len=0; for(int i=0;i<n;i++){ cin>>x>>s; key=BKDRHash(s)%maxn; for(int j=0;j<q[key].size();j++){ if(strcmp(q[key][j].name,s)==0){ q[key][j].price+=x; if(strcmp(s,"memory")==0) mom=q[key][j].price; else p[len++]=q[key][j].price; break; } } } for(int i=0;i<len;i++) if(p[i]>mom) rank++; cout<<rank+1<<endl; } } return 0; }
P3370 【模板】字符串哈希
#include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 10010; typedef unsigned long long ull; char s[N]; ull h[N],ans[N]; const int pp=131; int n; ull calc(char *s,int n){ h[0]=0; for(int i=1;i<=n;i++) h[i]=h[i-1]*pp+s[i]; return h[n]; } int main(){ cin>>n; for(int i=1;i<=n;i++){ scanf("%s",s+1); int m=strlen(s+1); ans[i]=calc(s,m); } sort(ans+1,ans+1+n); int cnt=0; for(int i=1;i<=n;i++){ if(ans[i]!=ans[i-1]) cnt++; } printf("%d",cnt); return 0; }
最小表示法
#include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 7e5; typedef long long LL; int n; int s[N]; //最小表示法 int get_min(){ for(int i=1;i<=n;i++) s[n+i]=s[i]; //循环就放两倍长 int i=1,j=2,k=0; while(i<=n&&j<=n){ for(k=0;k<n&&s[i+k]==s[j+k];k++); s[i+k]>s[j+k] ? i=i+k+1:j=j+k+1; if(i==j) j++; } return min(i,j); } int main(){ scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&s[i]); int k=get_min(); for(int i=0;i<n;i++){ //注意下标 printf("%d ",s[k+i]); } return 0; }
字典树Trie树
时间复杂度:查找和插入单词的复杂度都是O(M),M是待插入/待查找单词的长度
空间复杂度:公共前缀只存一次
应用:
(1)字符串检索
(2)词频统计
(3)字符串排序:插入的时候,在树的平级按顺序插入,建好之后先序遍历即可
(4)前缀匹配
例如: hdu 1251 统计难题 求以某个字符串 为前缀的单词数量
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1010; const int INF=0x3fffffff; typedef long long LL; ///字典树 //求以某个字符串 为前缀的单词数量 int trie[1000010][26]; //用数组定义字典树,!!!存储下一个字符的位置 int num[1000010]={0}; //单词数量 int pos=1; //当前新分配的存储位置,其实就是所有出现的 void inser(char str[]){ int p=0; for(int i=0;str[i];i++){ int n=str[i]-'a'; if(trie[p][n]==0) trie[p][n]=pos++; //不存在就分配一个 p=trie[p][n]; //向下延旭 num[p]++; //以此为前缀的单词数量++ } } int fin(char str[]){ int p=0; for(int i=0;str[i];i++){ //如果存在这个公共前缀,那么肯定能够遍历完 int n=str[i]-'a'; if(trie[p][n]==0) return 0; //不存在 p=trie[p][n]; //cout<<p<<endl; } return num[p]; } int main(){ char aa[11]; while(gets(aa)){ if(!strlen(aa)) break; inser(aa); } while(gets(aa)){ cout<<fin(aa)<<endl; } return 0; }
KMP算法
单模匹配算法,复杂度为O(n+m),主要就是对模式串求出next数组,然后与文本串匹配,在匹配时,文本串不会回退,next就是模式串匹配失败是回退的地方,所以复杂度低
例题: 剪花布条(要有个数)
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1010; const int INF=0x3fffffff; typedef long long LL; //KMP模板题 char str[maxn],pat[maxn]; int cnt; int Next[maxn]; void getnext(char *st,int len){ Next[0]=0; Next[1]=0; //初始化 for(int i=1;i<len;i++){ int j=Next[i]; while(j&&st[i]!=st[j]) j=Next[j]; Next[i+1]=(st[i]==st[j])?j+1:0; } } void kmp(char *s,char *p){ int last=-1; //为什么要这个,看例子就知道了,这个是最后一次得到一个完整的模式串的位置 int n=strlen(s); int m=strlen(p); getnext(p,m); int j=0; for(int i=0;i<n;i++){ while(j&&s[i]!=p[j]) j=Next[j]; if(s[i]==p[j]) j++; if(j==m){ if(i-last>=m){ cnt++; last=i; } } } } int main(){ while(~scanf("%s",str)){ if(str[0]=='#') break; scanf("%s",pat); cnt=0; kmp(str,pat); printf("%d\n",cnt); } return 0; }
模板
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> using namespace std; //KMP算法:匹配字符串,O(n+m) //next数组:含义是什么?next[i]就是所求最长相等前后缀的前缀最后一位的下标,用递推的方法来求 const int maxn=1001; int next[maxn]; string text,pattern; //文本串、模式串 void getnext(string s,int len) { //针对模式串的 int j=-1; next[0]=-1; //第一步:初始化 for(int i=1; i<len; i++) { //一开始有一个关于模式串的大循环 while(j!=-1&&s[i]!=s[j+1]) j=next[j]; //j不断回退(j=-1或s[i]==s[j+1] if(s[i]==s[j+1]) j++; //相等:长度加1 next[i]=j; } } //KMP算法:令i指向text当前需要匹配的一位,令j指向pattern当前已经匹配了的一位(text[i]==pattern[j+1]) //next[j]数组的意思是:如果第j+1为匹配失败是,应该回退的位置 //判断pattern是不是text的子串 bool KMP(string a,string b) { int n=a.length(),m=b.length(); getnext(b,m); //计算匹配串的next数组 int j=-1; //初始化,表示当前没有任意一位被匹配 for(int i=0; i<n; i++) { //匹配文本串的每一位 while(j!=-1&&a[i]!=b[j+1]) j=next[j]; if(a[i]==b[j+1]) j++; if(j==m-1) return 1; //如果匹配到了模式串的最后一位:匹配成功 //就没有了 } return 0; } //计算模式串出现的次数 int KMP2(string a,string b) { int n=a.length(),m=b.length(); getnext(b,m); //计算匹配串的next数组 int ans=0; int j=-1; //初始化,表示当前没有任意一位被匹配 for(int i=0; i<n; i++) { //匹配文本串的每一位 while(j!=-1&&a[i]!=b[j+1]) j=next[j]; if(a[i]==b[j+1]) j++; if(j==m-1) { ans++; //次数加1; j=next[j]; //让j回退到next[j]继续匹配 } //就没有了 } cout<<"the text has "<<ans<<" pattern "<<endl; } //KPM优化:将next数组换为 nextval[]数组,为了避免不必要的回退!! //nextval数组的含义是第j+1位匹配失败了,第j位应该退回的最佳位置 //失去了next数组本身的含义,但是降低了时间复杂度 int nextval[maxn]; void getnextval(string a,int len){ int j=-1; nextval[0]=-1; //初始化 for(int i=1;i<len;i++){ while(j!=-1&&a[i]!=a[j+1]) j=nextval[j]; if(a[i]==a[j+1]) j++; //next数组的情况:直接:next[i]=j; if(j==-1||a[i+1]!=a[j+1]) nextval[i]=j; //这是不需要回退的情况 else nextval[i]=nextval[j]; } //i+1不需要判断是否越界:不必要(思考p463) } int main() { cin>>text>>pattern; if(KMP(text,pattern)==1) cout<<"YES"<<endl; else cout<<"NO"<<endl; return 0; }
AC自动机
多模匹配算法,在一个文本串中匹配查找多个子串,把所有的模式串P弄成字典树,复杂度为O(km+nm)
这个算法的理解是:回退边和转移边
视频讲的也很好:187 AC自动机 - 董晓 - 博客园 (cnblogs.com)
#include <queue> #include <cstdlib> #include <cmath> #include <cstdio> #include <string> #include <cstring> #include <iostream> #include <algorithm> using namespace std; typedef long long ll; const int maxn = 2*1e6+9; int trie[maxn][26]; //字典树 int cntword[maxn]; //记录该单词出现次数 int fail[maxn]; //失败时的回溯指针 int cnt = 0; void insertWords(string s){ int root = 0; for(int i=0;i<s.size();i++){ int next = s[i] - 'a'; if(!trie[root][next]) trie[root][next] = ++cnt; root = trie[root][next]; } cntword[root]++; //当前节点单词数+1 } void getFail(){ queue <int>q; for(int i=0;i<26;i++){ //将第二层所有出现了的字母扔进队列 if(trie[0][i]){ fail[trie[0][i]] = 0; q.push(trie[0][i]); } } //fail[now] ->当前节点now的失败指针指向的地方 ////tire[now][i] -> 下一个字母为i+'a'的节点的下标为tire[now][i] while(!q.empty()){ int now = q.front(); q.pop(); for(int i=0;i<26;i++){ //查询26个字母 if(trie[now][i]){ //如果有这个子节点为字母i+'a',则 //让这个节点的失败指针指向(((他父亲节点)的失败指针所指向的那个节点)的下一个节点) //有点绕,为了方便理解特意加了括号 fail[trie[now][i]] = trie[fail[now]][i]; q.push(trie[now][i]); } else//否则就让当前节点的这个子节点 //指向当前节点fail指针的这个子节点 trie[now][i] = trie[fail[now]][i]; } } } int query(string s){ int now = 0,ans = 0; for(int i=0;i<s.size();i++){ //遍历文本串 now = trie[now][s[i]-'a']; //从s[i]点开始寻找 for(int j=now;j && cntword[j]!=-1;j=fail[j]){ //一直向下寻找,直到匹配失败(失败指针指向根或者当前节点已找过). ans += cntword[j]; cntword[j] = -1; //将遍历国后的节点标记,防止重复计算 } } return ans; } int main() { int n; string s; cin >> n; for(int i=0;i<n;i++){ cin >> s ; insertWords(s); } fail[0] = 0; getFail(); cin >> s ; cout << query(s) << endl; return 0; }