ACM模板 字符串
2017-07-24 20:25
357 查看
@(ACM模板)
KMP
MP算法
KMP算法
KMP求循环节
扩展KMP
hash函数
生成hash函数
字符串匹配
Manacher算法求最长回文子串
后缀数组
Trie树
AC自动机
对于文本串T和模式串P,判断P是否为T的子串,若是,则返回匹配位置
复杂度O(n+m),其中n和m分别为T和P的长度
若P非T的子串,返回-1
字符数组的下标从0开始,lost的下标从0开始
kmp返回值下标从1开始(因为符合通常习惯)
下面计算了字符数组的hash值,要求s[l]…s[r]这个字串的hash,用getHash(l,r)即可
h1=s1
h2=s1∗b1+s2
h3=s1∗b2+s2∗b1+s3
⋯
hr=s1∗br−1+s2∗br−2+⋯+sl−1∗br−1+1+⋯+sr
hl−1=s1∗bl−2+s2∗bl−3+⋯+sl−1
hr−hl−1∗br−1+1=sl∗br−l+s2∗br−l−1+⋯+sr
注意:
- 字符数组下标从1开始
2. 字符串匹配
1中的代码加上下面的函数
KMP
MP算法
KMP算法
KMP求循环节
扩展KMP
hash函数
生成hash函数
字符串匹配
Manacher算法求最长回文子串
后缀数组
Trie树
AC自动机
1. KMP
1. MP算法
此为MP算法,KMP对fail数组进行了优化对于文本串T和模式串P,判断P是否为T的子串,若是,则返回匹配位置
复杂度O(n+m),其中n和m分别为T和P的长度
若P非T的子串,返回-1
字符数组的下标从0开始,lost的下标从0开始
kmp返回值下标从1开始(因为符合通常习惯)
#include<bits/stdc++.h> namespace KMP { const int maxn = 1e6 + 5;//字符串长度 int fail[maxn];//fail指针,fail[i]代表i失配后,前面的字符串s[0...i-1]中,满足”前缀等于后缀“的前缀里,最长的前缀的位置加一 //也就是说,fail指针指向的是失配后“待匹配”的位置 void getFail(char *P) { int m = strlen(P); fail[0] = fail[1] = 0; for(int i = 1; i < m; ++ i) //寻找位置i“前缀等于后缀”的最大长度,作为fail[i+1] { //先不管字符i,找前面的“前缀等于后缀”的最大长度 int j = fail[i]; //然后比较位置i //重复这个过程直到匹配 while(j && P[i] != P[j]) j = fail[j]; //若一直匹配不到,j会逐渐减小到0 //这时候需要判断一下是否匹配到了 fail[i + 1] = (P[i] == P[j])? j + 1 : 0; } } int finda(char *T, char *P)//T为文本串,P为模式串 //有解返回开始位置,无解返回-1 { int n = strlen(T); int m = strlen(P); getFail(P); int j = 0;//模式串的待匹配结点 for(int i = 0; i < n; ++ i)//文本串当前指针 { while(j && P[j] != T[i]) j = fail[j];//顺着fail指针走,直到可以匹配or走到头 if(P[j] == T[i]) ++ j;//更新待匹配位置 if(j == m)//全部都匹配完了 return i - m + 1 + 1;//返回1-indexed的位置 } return -1; } }
2. KMP算法
待整理。。。。#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> using namespace std; #define N 100010 char str1 , str2 ; int nextval ; int lens, lenp; void getnext(const char *p, int nextval[]) //前缀函数(滑步函数) { int i = 0, j = -1; nextval[0] = -1; while(i != lenp) { if(j == -1 || p[i] == p[j]) //(全部不相等从新匹配 || 相等继续下次匹配) { //++i,++j之后,再次判断p[i]与p[j]的关系 ++i, ++j; if(p[i] != p[j]) //abcdabce nextval[i] = j; //next[i] = next[j]; //这里其实是优化了后的,也可以仍是next[i]=j //当str[i]==str[j]时,如果str[i]匹配失败,那么换成str[j]肯定也匹配失败,所以不是令next[i]=j,而是next[i] = next[j],跳过了第j个字符, //即省去了不必要的比较,优化前的next[i]表示前i个字符中前缀与后缀相同的最大长度 else //abcabca nextval[i] = nextval[j]; } else j = nextval[j]; //子串移动到第nextval[j]个字符和主串相应字符比较 } cout<<"前缀函数为:"<<endl; for(int i = 0; i < lenp; ++i) printf("%d", nextval[i]); cout<<endl; } int KMP(char *s, char *p, int nextval[]) //KMP算法 { int i = 0, j = 0; //s和j字符串从头开始比较 while(i != lens && j != lenp) { if(s[i] == p[j]) //相等继续匹配 ++i, ++j; else { if(nextval[j] == -1) //-1代表与p[0]间接比较过,需要主串后移,p重新从头匹配 ++i, j = 0; else j = nextval[j]; //直接右移nextval[j]位与s[i]比较 } } if(j == lenp) //返回从主串第几个元素开始匹配 return i - j; else return -1; } int main() //主串子串位置从0开始 { int pos; while(~scanf("%s%s", str1, str2)) //str1为主串,str2为子串 { lens = strlen(str1); lenp = strlen(str2); if(lens < lenp) //主串长度<子串长度 { printf("主串长度不应小于子串长度!\n"); continue; } getnext(str2, nextval); //求子串的前缀函数 pos = KMP(str1, str2, nextval); if(pos == -1) printf("主串中不含有子串\n"); else printf("子串从主串的第 %d 个元素开始匹配\n", pos); } return 0; }
3. KMP求循环节
注释部分为求前缀循环节const int maxn = 1e6+5; int fail[maxn]; char s[maxn]; void getFail(char* P) { int m = strlen(P); fail[0] = fail[1] = 0; for(int i = 1; i < m; i++) { int j = fail[i]; while(j && P[i] != P[j]) j = fail[j]; fail[i+1] = P[i] == P[j] ? j+1 : 0; } } int repetend(char* s) { getFail(s); int n = strlen(s); int len;//循环节长度 int period;//循环节周期数 //下面三段代码选择一段 //01. 求该字符串的循环节 len = n - fail ; period = n/len; if(n % len== 0) return len; else return n; //02. 求该字符的前缀的循环节 // for(int i = 2; i <= n; i++)//考察长度为i的前缀 { len = i - fail[i];//循环节长度 period = i/len;//循环节周期数 if(i != len && i % len == 0) printf("%d %d %d\n", i, len, period); } //03. 求该字符最少在结尾补上几个字符,使其成为周期循环字符串,且周期数大于1 len = n - fail ; if(len != n && n % len== 0) return 0; else return len - fail % len; //取余的作用:abcab,去掉abc }
3. 扩展KMP
#include<bits/stdc++.h> using namespace std; typedef long long LL; const int maxn = 1e6 + 5; char s1[maxn], s2[maxn]; struct ExtendKMP { //模式串P(Pattern)长度m,文本串T(Text)长度n //nxt[i]:T[i..n-1]与T的LCP长度 //extend[i]:P[i..m-1]与T的LCP长度 int nxt[maxn], extend[maxn]; void getNext(char *P) { int m = strlen(P); nxt[0] = m; int i = 0; while(P[i] == P[i+1]) ++i; nxt[1] = i; int id = 1; for(i = 2; i < m; ++ i) { if(nxt[i-id] + i < id + nxt[id]) nxt[i] = nxt[i-id]; else { int j = nxt[id] + id - i; if(j < 0) j = 0; while(i+j < m && P[j] == P[j+i]) ++j; nxt[i] = j; id = i; } } } void getExtend(char *P, char *T) { int m = strlen(P); int n = strlen(T); getNext(T); int i = 0; while(i < m && i < n && P[i] == T[i]) ++i; extend[0] = i; int id = 0; for(int i = 1; i < m; ++i) { if(nxt[i-id]+i < extend[id]+id) extend[i] = nxt[i-id]; else { int j = extend[id] + id - i; if(j < 0) j = 0; while(i + j < m && j < n && P[j+i] == T[j]) ++j; extend[i] = j; id = i; } } } };
2. hash函数
1. 生成hash函数下面计算了字符数组的hash值,要求s[l]…s[r]这个字串的hash,用getHash(l,r)即可
h1=s1
h2=s1∗b1+s2
h3=s1∗b2+s2∗b1+s3
⋯
hr=s1∗br−1+s2∗br−2+⋯+sl−1∗br−1+1+⋯+sr
hl−1=s1∗bl−2+s2∗bl−3+⋯+sl−1
hr−hl−1∗br−1+1=sl∗br−l+s2∗br−l−1+⋯+sr
注意:
- 字符数组下标从1开始
typedef unsigned long long ull; const int maxn = 1e5+7; const ull base = 163; char s[maxn]; ull hah[maxn]; ull pw[maxn]; void calcHash(char* s) { pw[0] = 1; hah[0] = 0; int n = strlen(s+1); for(int i = 1; i < maxn; i++) pw[i] = pw[i-1] * base; for(int i = 1; i <= n; i++) hah[i] = hah[i-1] * base + s[i]; } ull getHash(int l, int r) { return hah[r] - hah[l-1] * pw[r-l+1]; } int main() { scanf("%s", s+1); calcHash(s); return 0; }
2. 字符串匹配
1中的代码加上下面的函数
int strMatch(char* T, char* P) { int n = strlen(T+1), m = strlen(P+1); initHash(T, hah); initHash(P, hah2); int h = getHash(1, m, hah2); for(int i = 1; i + m - 1 <= n; i++) if(getHash(i, i+m-1, hah) == h) return i; return -1; } int main() { scanf("%s%s", T+1, P+1);//start with 1!!! return 0; }
4. Manacher算法(求最长回文子串)
const int maxn = 1e3+5; int p[maxn]; string Manacher(string s) { string t = "@#"; for (int i = 0; i < s.size(); ++i) { t += s[i]; t += "#"; } memset(p, 0, sizeof p); int mx = 0, id = 0, resLen = 0, resCenter; for(int i = 1; i < t.size(); ++i) { p[i] = mx>i ? min(p[2*id-i], mx-i) : 1; while(t[i+p[i]] == t[i-p[i]]) ++p[i]; if(mx < i+p[i]) { mx = i + p[i]; id = i; } if(resLen < p[i]) { resLen = p[i]; resCenter = i; } } return s.substr((resCenter - resLen) / 2, resLen-1 ); }
5. 后缀数组
#include<bits/stdc++.h> using namespace std; typedef long long LL; const int maxn = 1e5+7; char s[maxn]; int sa[maxn], t[maxn], t2[maxn], c[maxn], n; void build_sa(int m) { b int *x = t, *y = t2; //index sort for(int i = 0; i < m; ++i) c[i] = 0; for(int i = 0; i < n; ++i) ++c[x[i] = s[i]]; for(int i = 1; i < m; ++i) c[i] += c[i-1]; for(int k = 1; k <= n; k <<= 1) { int p = 0; //直接利用sa数组排序第二关键字 for(int i = n - k; i < n; ++i) if(sa[i] >= k) y[p++] = sa[i] - k; //基数排序第一关键字 for(int i = 0; i < m; ++i) c[i] = 0; for(int i = 0; i < n; ++i) ++c[x[y[i]]]; for(int i = 0; i < m; ++i) c[i] += c[i-1]; for(int i = n - 1; i >= 0; --i) sa[--c[x[y[i]]]] = y[i]; //根据sa和y数组计算新的x数组 swap(x, y); p = 1; x[sa[0]] = 0; for(int i = 1; i < n; ++i) x[sa[i]] = (y[sa[i-1]] == y[sa[i]] && y[sa[i-1]+k] == y[sa[i]+k]) ? (p-1) : p++; if(p >= n) break; m = p; } } int m;//模板长度。简单起见存为全局变量 int cmp_suf(char *pattern, int p)//判断模板s是否为后缀p的前缀 { return strncmp(pattern, s + sa[p], m); } int finda(char *P) { m = strlen(P); if(cmp_suf(P, 0) < 0 || cmp_suf(P, n-1) > 0) return -1; int l = 0, r = n-1; while(l <= r) { int mid = (l + r) >> 1; int res = cmp_suf(P, mid); if(!res) return mid; if(res < 0) r = mid - 1; else l = mid + 1; } return -1; } int main() { return 0; }
6. Trie树
注意,根据Trie节点中内容的不同(如下面代码中为小写字母),需要注意maxm的不同、字符到id的映射关系不同const int maxn = 1e5+7;//number of letters const int maxm = 26;//size of lower case letters //a Trie of lower case strings struct Trie { int ch[maxn][maxm]; int val[maxn];//assume that val is positive int tot;//节点总数 Trie() { tot = 1; memset(ch[0], 0, sizeof ch[0]); } //insert an string s, whose value is v; note that v != 0. 0 stands for "not an end point" void add(char *s, int v) { int u = 0;//root int n = strlen(s); for(int i = 0; i < n; ++i) { int id = s[i] - 'a'; if(!ch[u][id])//the point does not exist { memset(ch[tot], 0, sizeof ch[tot]); val[tot] = 0;//the val of middle point is 0 ch[u][id] = tot++; } u = ch[u][id]; } val[u] = v; } int finda(char *s)//return -1 if not exists { int u = 0;//root; int n = strlen(s); for(int i = 0; i < n; ++i) { int id = s[i] - 'a'; if(!ch[u][id]) return 0; u = ch[u][id]; } return val[u]; } };
7. AC自动机
#include<bits/stdc++.h> using namespace std; typedef long long LL; const int maxn = 1e6 + 5; const int maxm = 26; struct ACautomaton { int ch[maxn][maxm];//ch[i][c]代表结点i的c孩子;初始有一个根节点,代表空字符串 int val[maxn];//val为正代表这是一个模式串单词结点 int fail[maxn];//suffix link,代表当前路径字符串的最大前缀 int last[maxn];//output link, 上一个单词结点 int tot;//Trie树中结点总数 void init() { tot = 1; val[0] = 0; memset(ch[0], 0, sizeof ch[0]); } //O(n),n为所有模式总长度 void add(char *P, int v)//插入模式串,值为v { int u = 0;//当前结点 int n = strlen(P); for(int i = 0; i < n; ++i) { int c = P[i] - 'a'; if(!ch[u][c])//若当前结点无c孩子,则创造一个 { memset(ch[tot], 0, sizeof ch[tot]); val[tot] = 0;//中间结点的值为零 ch[u][c] = tot++; } u = ch[u][c];//走向当前结点的c孩子 } //现在走到了模式串的结尾结点 val[u] += v; } //O(tot)的 void getFail()//构造fail指针和last指针 //使用BFS,因为fail指针一定指向长度更短的字符串 { queue<int> q; fail[0] = 0; //初始化队列 for(int c = 0; c < maxm; ++c) { int u = ch[0][c]; if(u) { fail[u] = last[u] = 0;//第一层结点的fail都是根节点 q.push(u);//将第一层结点加入队列 } } //BFS while(!q.empty()) { int cur = q.front(); q.pop(); for(int c = 0; c < maxm; ++c)//为cur结点的c孩子添加fail指针 { int u = ch[u][c]; if(!u)//当前结点没有c孩子 { ch[cur][c] = ch[fail[cur]][c];//沿fail往上找,因为fail指针指向的还是这个后缀 continue; } q.push(u);//c孩子入队 int v = fail[cur]; while(v && !ch[v][c]) v = fail[v];//若后缀结点无c孩子,就沿fail指针一直网上找 fail[u] = ch[v][c];//给c孩子添加fail指针 //若c孩子的fail指针指向模式串结点,则c孩子的last指向fail指针位置即可,因为这就是最长的 //否则指向fail指针指向的结点的last即可 if(val[fail[u]]) last[u] = fail[u]; else last[u] = last[fail[u]]; } } } };
相关文章推荐
- 邝斌的ACM模板(字符串 HASH)
- 邝斌的ACM模板(求 A^B 的约数之和对 MOD 取模)
- 邝斌的ACM模板(树链剖分)
- ACM 模板列表
- ACM模板——快速判断素数
- HDU 5510 Bazinga 字符串HASH (2015ACM/ICPC亚洲区沈阳站)
- 杭电 acm 2017 字符串统计
- hdu 5047 Sawtooth--2014acm上海赛区邀请赛(附java模板)
- ACM模板——最长公共子序列 LCS
- ACM-高精度模板(综合篇)
- cf244D. Match & Catch 字符串hash (模板)或 后缀数组。。。
- Gson利用泛型将Http请求返回的Json字符串快速解析模板
- ACM日记_17.7.22——多重背包模板
- ACM-字符串-字典树
- 【模板】字符串哈希
- 邝斌的ACM模板(FFT)
- ES6, Angular,React和ABAP中的String Template(字符串模板)
- Codeforces 25E 字符串hash模板题
- POJ3461【1e6 字符串 hash模板】
- ACM算法模板