您的位置:首页 > 其它

ACM模板 字符串

2017-07-24 20:25 357 查看
@(ACM模板)

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 字符串