ACM模板 字符串

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

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

@(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) 字典序全排列 字典序组合 组合公式
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值