ACM模板 字符串

本文深入解析了多种经典的字符串处理算法,包括KMP算法及其扩展、Manacher算法、后缀数组构建、Trie树和AC自动机等。通过实例代码详细介绍了每种算法的工作原理、实现细节及应用场景。

@(ACM模板)

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[N], str2[N];  
int nextval[N];  
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求循环节
  1. 注释部分为求前缀循环节
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[n];
    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[n];
    if(len != n && n % len== 0) return 0;
    else
        return len - fail[n] % 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=s1b1+s2
h3=s1b2+s2b1+s3

hr=s1br1+s2br2++sl1br1+1++sr
hl1=s1bl2+s2bl3++sl1
hrhl1br1+1=slbrl+s2brl1++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]];
            }
        }
    }
};
几何\ 多边形 多边形切割 浮点函数 几何公式 面积 球面 三角形 三维几何 凸包(graham) 网格(pick) 圆 整数函数 注意 结构\ 并查集 并查集扩展(friend_enemy) 堆(binary) 堆(mapped) 矩形切割 线段树 线段树扩展 线段树应用 子段和 子阵和 其他\ 大数(整数类封装) 分数 矩阵 线性方程组(gauss) 日期 线性相关 数论\ 阶乘最后非零位 模线性方程(组) 质数表 质数随机判定(miller_rabin) 质因数分解 最大公约数欧拉函数 数值计算\ 定积分计算(Romberg) 多项式求根(牛顿法) 周期性方程(追赶法) 图论_NP搜索\ 最大团(n小于64) 最大团 图论_连通性\ 无向图关键边(dfs邻接阵形式) 无向图关键点(dfs邻接阵形式) 无向图块(bfs邻接阵形式) 无向图连通分支(bfs邻接阵形式) 无向图连通分支(dfs邻接阵形式) 有向图强连通分支(bfs邻接阵形式) 有向图强连通分支(dfs邻接阵形式) 有向图最小点基(邻接阵形式) 图论_匹配\ 二分图最大匹配(hungary邻接表形式) 二分图最大匹配(hungary邻接阵形式) 二分图最大匹配(hungary邻接表形式,邻接阵接口) 二分图最大匹配(hungary正向表形式) 二分图最佳匹配(kuhn_munkras邻接阵形式) 一般图最大匹配(邻接表形式) 一般图最大匹配(邻接阵形式) 一般图最大匹配(正向表形式) 一般图匹配(邻接表形式,邻接阵接口) 图论_网络流\ 上下界最大流(邻接阵形式) 上下界最小流(邻接阵形式) 上下界最大流(邻接表形式) 上下界最小流(邻接表形式) 最大流(邻接阵形式) 最大流(邻接表形式) 最大流(邻接表形式,邻接阵接口) 最大流无流量(邻接阵形式) 最小费用最大流(邻接阵形式) 图论_应用\ 欧拉回路(邻接阵形式) 前序表转化 树的优化算法 拓扑排序(邻接阵形式) 最佳边割集 最佳顶点割集 最小边割集 最小顶点割集 最小路径覆盖 图论_最短路径\ 最短路径(单源bellman_ford邻接阵形式) 最短路径(单源dijkstra邻接阵形式) 最短路径(单源dijkstra_bfs邻接表形式) 最短路径(单源dijkstra_bfs正向表形式) 最短路径(单源dijkstra+binary_heap邻接表形式) 最短路径(单源dijkstra+binary_heap正向表形式) 最短路径(单源dijkstra+mapped_heap邻接表形式) 最短路径(单源dijkstra+mapped_heap正向表形式) 最短路径(多源floyd_warshall邻接阵形式) 图论_支撑树\ 最小生成树(kruskal邻接表形式) 最小生成树(kruskal正向表形式) 最小生成树(prim邻接阵形式) 最小生成树(prim+binary_heap邻接表形式) 最小生成树(prim+binary_heap正向表形式) 最小生成树(prim+mapped_heap邻接表形式) 最小生成树(prim+mapped_heap正向表形式) 最小树形图(邻接阵形式) 应用\ joseph模拟 N皇后构造解 布尔母函数 第k元素 幻方构造 模式匹配(kmp) 逆序对数 字符串最小表示 最长公共单调子序列 最长子序列 最大子串匹配 最大子段和 最大子阵和 组合\ 排列组合生成 生成gray码 置换(polya) 字典序全排列 字典序组合 组合公式
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值