KMP,Trie,AC自动机题目集

字符串算法并不多,KMP,trie,AC自动机就是其中几个最经典的。字符串的题目灵活多变也有许多套路,需要多做题才能体会。这里收集了许多前辈的题目做个集合,方便自己回忆。

KMP题目:https://blog.csdn.net/qq_38891827/article/details/80501506

Trie树题目:https://blog.csdn.net/qq_38891827/article/details/80532462

AC自动机:模板https://www.luogu.org/blog/42196/qiang-shi-tu-xie-ac-zi-dong-ji

AC自动机题目集:https://www.cnblogs.com/kuangbin/p/3164106.html

KMP:

LuoguP3375 KMP模板

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1000000+10;
char a[N],b[N];
int n,m,nxt[N],f[N],g[N];

void get_next() {
    nxt[1]=0;
    for (int i=2,j=0;i<=n;i++) {
        while (j>0 && a[j+1]!=a[i]) j=nxt[j];
        if (a[j+1]==a[i]) j++;
        nxt[i]=j;
    }
}

void KMP() {
    for (int i=1,j=0;i<=m;i++) {
        while (j>0 && (j==n || b[i]!=a[j+1])) j=nxt[j];
        if (b[i]==a[j+1]) j++;
        f[i]=j;
        if (f[i]==n) g[i]=1;  //这就是A在B中的某一次出现
    }
}

int main()
{
    scanf("%s",b+1); m=strlen(b+1);
    scanf("%s",a+1); n=strlen(a+1);
    get_next();
    KMP();
    for (int i=1;i<=m;i++)
        if (g[i]==1) cout<<i-n+1<<endl;
    for (int i=1;i<=n;i++) cout<<nxt[i]<<" ";
    return 0;
}

HDU-2087

给出A串和B串,问B串在A串中出现次数。不能重叠!!!KMP裸题,允许重叠的话匹配成功时j=nxt[j],不允许重叠的话匹配成功时j=0。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1e3+10;
char a[N],b[N];
int n,m,nxt[N],f[N],g[N];

void get_next() {
    nxt[1]=0;
    for (int i=2,j=0;i<=n;i++) {
        while (j>0 && a[j+1]!=a[i]) j=nxt[j];
        if (a[j+1]==a[i]) j++;
        nxt[i]=j;
    }
}

int KMP() {
    int ret=0;
    for (int i=1,j=0;i<=m;i++) {
        while (j>0 && (j==n || b[i]!=a[j+1])) j=nxt[j];
        if (b[i]==a[j+1]) j++;
        f[i]=j;
        if (f[i]==n) ret++,j=0;  //匹配成功:因为不允许重叠所以要把j=0
    }
    return ret;
}

int main()
{
    while (scanf("%s",b+1) && b[1]!=‘#‘) {
        memset(nxt,0,sizeof(nxt));
        scanf("%s",a+1);
        n=strlen(a+1); m=strlen(b+1);
        get_next();
        cout<<KMP()<<endl;
    }
    return 0;
}

POJ-1961/HDU-1358

给出一个串,问每个前缀的最小循环节以及循环节大小。KMP经典题,对于s[1-i],最小循环节就是i-nxt[i],循环节大小就是i/(i-nxt[i])。

#include<iostream>
#include<cstdio>
using namespace std;
const int N=1000000+10;
char S[N];
int n,nxt[N],f[N];

void get_next() {
    nxt[1]=0;
    for (int i=2,j=0;i<=n;i++) {
        while (j>0 && S[j+1]!=S[i]) j=nxt[j];
        if (S[j+1]==S[i]) j++;
        nxt[i]=j;
    }
}

int main()
{
    int T=0;
    while (scanf("%d",&n) && n) {
    printf("Test case #%d\n", ++T);
        scanf("%s",S+1);
        get_next();
        for (int i=2;i<=n;i++)
            if (i%(i-nxt[i])==0 && i/(i-nxt[i])>1)
                printf("%d %d\n",i,i/(i-nxt[i]));
        printf("\n");
    }
    return 0;
}

HDU-3746

添加最少的字符使原字符串变成周期至少为2的循环字符串。求出最小循环节lop,当且仅当n%lop==0 && n/lop>1才不需要添加,否则添加 lop-n%lop个字符。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1000000+10;
char S[N];
int n,nxt[N],f[N];

void get_next() {
    nxt[1]=0;
    for (int i=2,j=0;i<=n;i++) {
        while (j>0 && S[j+1]!=S[i]) j=nxt[j];
        if (S[j+1]==S[i]) j++;
        nxt[i]=j;
    }
}

int main()
{
    int T; scanf("%d",&T);
    while (T--) {
        scanf("%s",S+1); n=strlen(S+1);
        for (int  i=0;i<=n;i++) nxt[i]=0;
        get_next();
        int lop=n-nxt[n];
        if (n%lop==0 && n/lop>1) puts("0");
        else printf("%d\n",lop-n%lop);
    }
    return 0;
}

POJ-2752

给定一个字符串,输出该串所有的前后缀相同的前缀位置。正好nxt数组就是前后缀相同大小,但是注意到nxt只记录了最大前后缀,但是题目要求输出所有前后缀,所以我们递归输出所以的nxt即可。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=4e5+10;
char a[N],b[N];
int n,m,nxt[N],f[N],g[N];

void get_next() {
    nxt[1]=0;
    for (int i=2,j=0;i<=n;i++) {
        while (j>0 && a[j+1]!=a[i]) j=nxt[j];
        if (a[j+1]==a[i]) j++;
        nxt[i]=j;
    }
}

void dfs(int x) {
    if (x==0) return;
    dfs(nxt[x]);
    printf("%d ",x);
}

int main()
{
    while (scanf("%s",a+1)!=EOF) {
        n=strlen(a+1);
        for (int i=1;i<=n;i++) nxt[i]=0;
        get_next();
        dfs(n); puts("");
    }
    return 0;
}

Trie:

HDU-1251

先给出一堆单词然后询问,每个询问输入一个字符串s问以s为前缀的单词个数。询问有多少个单词是s的前缀的话就只在单词结束点++,询问s是多少个单词的前缀的话就在单词每一个点都+1代表访问次数。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1000000+10;
int trie[2*N][26],tot=1;
int n,m,endp[2*N];

void insert(char *str) {
    int p=1;
    for (int i=0;i<strlen(str);i++) {
        int x=str[i]-‘a‘;
        if (!trie[p][x]) trie[p][x]=++tot;
        p=trie[p][x];
        endp[p]++;  //访问次数
    }
    //endp[p]++;  //单词个数
}

int search(char *str) {
    int p=1;
    for (int i=0;i<strlen(str);i++) {
        int x=str[i]-‘a‘;
        p=trie[p][x];
        if (p==0) break;
    }
    return endp[p];
}

int main()
{
    char s[15];
    bool ok=0;
    while (gets(s)) {
        if (s[0]==‘\0‘) {ok=1; continue;}
        if (!ok) insert(s);
        else printf("%d\n",search(s));
    }
    return 0;
} 

HDU-2072

问不同单词个数,一边查询只有没出现过的单词插入即可。

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int trie[2*N][26],tot=1;
int n,m,endp[2*N];
string ss,s,str1;

void insert(string str) {
    int p=1;
    for (int i=0;i<str.length();i++) {
        int x=str[i]-‘a‘;
        if (!trie[p][x]) trie[p][x]=++tot;
        p=trie[p][x];
    }
    endp[p]++;  //单词个数
}

int search(string str) {
    int p=1;
    for (int i=0;i<str.length();i++) {
        int x=str[i]-‘a‘;
        p=trie[p][x];
        if (p==0) break;
    }
    return endp[p];
}

int main()
{
    ios::sync_with_stdio(false);
    while(getline(cin,str1)) {
        if(str1=="#") break;
        stringstream ss(str1);
        int ans=0;
        while (ss>>s) {
            if (search(s)==0) ans++,insert(s);
        }
        printf("%d\n",ans);
        for (int i=1;i<=tot;i++) {
            endp[i]=0;
            for (int j=0;j<=26;j++) trie[i][j]=0;
        }
        tot=1;
    }
    return 0;
} 

POJ-2001

给一堆单词问每个单词独有的前缀。先把全部单词插入到trie树中,trie树的endp[i]代表该点被访问次数,询问时直到访问次数为1的就是该单词独有的。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1000+10;
int trie[20*N][26],tot=1;
int n,m,num=1,endp[20*N];
char s[1010][25];

void insert(char *str) {
    int p=1;
    for (int i=0;i<strlen(str);i++) {
        int x=str[i]-‘a‘;
        if (!trie[p][x]) trie[p][x]=++tot;
        p=trie[p][x];
        endp[p]++;
    }
    //endp[p]++;  //单词个数
}

int search(char *str) {
    int p=1,ans=0;
    for (int i=0;i<strlen(str);i++) {
        int x=str[i]-‘a‘;
        p=trie[p][x];
        if (p==0) break;
        if (endp[p]==1) return i+1;
    }
    return strlen(str);
}

int main()
{
    while (scanf("%s",s[num])!=EOF) {
        insert(s[num]);
        num++;
    }
    num--;
    for (int i=1;i<=num;i++,puts("")) {
        int tmp=search(s[i]);
        printf("%s ",s[i]);
        for (int j=0;j<tmp;j++) printf("%c",s[i][j]);
    }
    return 0;
} 

POJ-3630

给一堆字符串,问是否存在某个单词是某个单词的前缀。老规矩先把全部单词插到trie树,然后询问看每次单词是否有出现次数>1的单词。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=10000+10;
int trie[10*N][26],tot=1;
int n,m,endp[10*N];
char s[N][12];

void insert(char *str) {
    int p=1;
    for (int i=0;i<strlen(str);i++) {
        int x=str[i]-‘0‘;
        if (!trie[p][x]) trie[p][x]=++tot;
        p=trie[p][x];
    }
    endp[p]++;  //单词个数
}

int search(char *str) {
    int p=1,ans=0;
    for (int i=0;i<strlen(str);i++) {
        int x=str[i]-‘0‘;
        p=trie[p][x];
        if (p==0) break;
        ans+=endp[p];
    }
    return ans;
}

int main()
{
    int T; cin>>T;
    while (T--) {
        scanf("%d",&n);
        for (int i=1;i<=n;i++) scanf("%s",s[i]),insert(s[i]);
        bool ok=1;
        for (int i=1;i<=n;i++)
            if (search(s[i])>1) { ok=0; break; }
        printf("%s\n",ok?"YES":"NO");

        for (int i=0;i<=tot;i++) {
            endp[i]=0;
            for (int j=0;j<=10;j++) trie[i][j]=0;
        }
        tot=1;
    }
    return 0;
} 

AC自动机:

洛谷P3808AC自动机模板1,求有多少个模式串在文本串里出现过。

#include<bits/stdc++.h>
using namespace std;
const int N=1000000+10;
int n,cnt=0;
char s[N];
struct node{
    int val;
    int vis[30];
    int fail;
}ac[N];

void Trie_init() { cnt=0; memset(ac[0].vis,0,sizeof(ac[0].vis)); }
int idx(char c) { return c-‘a‘; }

void Insert(char *s) {
    int u=0,len=strlen(s);
    for (int i=0;i<len;i++) {
        int c=idx(s[i]);
        if (!ac[u].vis[c]) {
            ac[u].vis[c]=++cnt;
            ac[cnt].val=ac[cnt].fail=0;
            memset(ac[cnt].vis,0,sizeof(ac[cnt].vis));
        }
        u=ac[u].vis[c];
    }
    ac[u].val++;
}

queue<int> q;
void Get_fail() {
    while (!q.empty()) q.pop();
    for (int i=0;i<26;i++) {
        int u=ac[0].vis[i];
        if (u) { ac[u].fail=0; q.push(u); }
    }
    while (!q.empty()) {
        int u=q.front(); q.pop();
        for (int i=0;i<26;i++) {
            int v=ac[u].vis[i];
            if (v) {
                ac[v].fail=ac[ac[u].fail].vis[i];
                q.push(v);
            } else ac[u].vis[i]=ac[ac[u].fail].vis[i];
        }
    }
}

int query(char *s) {
    int len=strlen(s);
    int u=0,ans=0;
    for (int i=0;i<len;i++)    {
        u=ac[u].vis[idx(s[i])];
        for (int t=u;t&&ac[t].val!=-1;t=ac[t].fail) {
            ans+=ac[t].val;
            ac[t].val=-1;
        }
    }
    return ans;
}

int main()
{
    Trie_init();
    scanf("%d",&n);
    for (int i=1;i<=n;i++) {
        scanf("%s",s);
        Insert(s);
    }
    Get_fail();
    scanf("%s",s);
    cout<<query(s)<<endl;
    return 0;
 } 

洛谷P3796AC自动机模板2,找出哪些模式串在文本串T中出现的次数最多并输出。

#include<bits/stdc++.h>
using namespace std;
const int N=1000000+10;
int n,cnt=0,ans[155];
char p[N],s[155][100];
struct node{
    int val;
    int vis[30];
    int fail;
}ac[N];

void Trie_init() { cnt=0; memset(ac[0].vis,0,sizeof(ac[0].vis)); }
int idx(char c) { return c-‘a‘; }

void Insert(char *s,int id) {
    int u=0,len=strlen(s);
    for (int i=0;i<len;i++) {
        int c=idx(s[i]);
        if (!ac[u].vis[c]) {
            ac[u].vis[c]=++cnt;
            ac[cnt].val=ac[cnt].fail=0;
            memset(ac[cnt].vis,0,sizeof(ac[cnt].vis));
        }
        u=ac[u].vis[c];
    }
    ac[u].val=id;
}

queue<int> q;
void Get_fail() {
    while (!q.empty()) q.pop();
    for (int i=0;i<26;i++) {
        int u=ac[0].vis[i];
        if (u) { ac[u].fail=0; q.push(u); }
    }
    while (!q.empty()) {
        int u=q.front(); q.pop();
        for (int i=0;i<26;i++) {
            int v=ac[u].vis[i];
            if (v) {
                ac[v].fail=ac[ac[u].fail].vis[i];
                q.push(v);
            } else ac[u].vis[i]=ac[ac[u].fail].vis[i];
        }
    }
}

int query(char *s) {
    int len=strlen(s);
    int u=0,ret=0;
    for (int i=0;i<len;i++)    {
        u=ac[u].vis[idx(s[i])];
        for (int t=u;t&&ac[t].val!=-1;t=ac[t].fail) {
            if (ac[t].val) ret=max(ret,++ans[ac[t].val]);  //ans[i]为模式串i的出现次数
        }
    }
    return ret;  //返回出现最多的次数
}

int main()
{
    while (scanf("%d",&n) && n) {
        Trie_init();
        for (int i=1;i<=n;i++) {
            scanf("%s",s[i]);
            Insert(s[i],i);
        }
        Get_fail();

        scanf("%s",p);
        memset(ans,0,sizeof(ans));
        int Max=query(p);
        printf("%d\n",Max);
        for (int i=1;i<=n;i++)
            if (ans[i]==Max) printf("%s\n",s[i]);
    }
    return 0;
 } 

洛谷P5357AC自动机模板3,求出每个模式串 Ti? 在 S 中出现的次数。这一题的重复单词有影响,所以我们不能用上一题记录单词出现次数,而是应该计算该位置的单词出现次数,然后对于每个单词记录它在Trie树上的位置,输出。

到这里也还不能够获得AC,会TLE。洛谷题解上有几种优化的方式,我这里选择的是拓扑排序优化建图的方式。

原文地址:https://www.cnblogs.com/clno1/p/10986068.html

时间: 2024-10-13 21:10:37

KMP,Trie,AC自动机题目集的相关文章

[AC自动机]题目合计

我只是想记一下最近写的题目而已喵~ 题解什么的才懒得写呢~ [poj 1625]Censored! 这题注意一个地方,就是输入数据中可能有 ASCII 大于 128 的情况,也就是说用 char 读入时,这个字符的值为负数,真是 RE 了好久…… 可以像我一样 map 党,你也可以把每个 s[i] 都加上 128 1 #include <cstdio> 2 #include <cstring> 3 #include <map> 4 #define max(x, y) (

字符串匹配相关模板(字典树、KMP、AC自动机)

字典树 struct Trie { int ch[MAXN][26]; int cnt; Trie() { cnt=1; memset(ch[0],0,sizeof(ch[0])); } int idx(char c) { return c-'a'; } void insert(char *s,int v) { int u=0,len=strlen(s); for (int i=0;i<len;i++) { int c=idx(s[i]); if (!ch[u][c]) { memset(ch[

从KMP到AC自动机

不想写题.不如写写算法总结? KMP 介(che)绍(dan) 以前都不知道\(KMP\)为什么叫\(KMP\),现在才明白:该算法是三位大牛:D.E.Knuth.J.H.Morris和V.R.Pratt同时发现的,以其名字首字母命名. \(KMP\)可以在\(O(n+m)\)的时间复杂度内解决判定一个字符串\(A[1\)~ \(N]\)是否为字符串\(B[1\)~\(M]\)的字串的问题. 虽然Hash好像也可以线性解决这个问题 我会暴力 当然一个\(O(nm)\)的做法是非常显然的:直接枚举

woj1572 Cyy and Fzz KMP / AC自动机 + DP

题目:http://acm.whu.edu.cn/land/problem/detail?problem_id=1572 题意:  有n个目标串,长度均小于15,(n<=8),现在随机写一个长度为L的字符串,问写下的这个字符串包含目标串的期望的个数. 比赛的时候还以为是水题,其实是自己太水.这种题一般是AC自动机的中等题,本题也可以用KMP做,结合状压dp. 方法一:AC自动机 建完Trie树后,就是跑一遍dp,注意单词节点要 |=(1<<val),会有重的字符串. dp过程: 用 dp

AC自动机及KMP练习

好久都没敲过KMP和AC自动机了.以前只会敲个kuangbin牌板子套题.现在重新写了自己的板子加深了印象.并且刷了一些题来增加自己的理解. KMP网上教程很多,但我的建议还是先看AC自动机(Trie图)的构造后再去理解.板子的话大家大同小异. 而AC自动机的构造则是推荐王贇的<Trie图的构建.活用与改进>. 前面的备用知识则是字典树.推荐董华星的<浅析字母树在信息学竞赛中的应用>.董聚聚不仅仅是介绍了字典树,包括一些常见的应用也有论述,介绍的挺详细的. 接下来就是刷题的部分了.

数据结构14——AC自动机

一.相关介绍 知识要求 字典树Trie KMP算法 AC自动机 多模式串的字符匹配算法(KMP是单模式串的字符匹配算法) 单模式串问题&多模式串问题 单模就是给你一个模式串,问你这个模式串是否在主串中出现过,这个问题可以用kmp算法高效完成: 多模就是给你多个模式串,问你有多少个模式串在这个主串中出现过. 若我们暴力地用每一个模式串对主串做kmp,这样虽然理论上可行,但是时间复杂度非常之高.而AC自动机算法就能高效地处理这种多模式串问题. 二.算法实现 [打基础] 失配指针fail 每个节点都有

AC自动机学习小结

AC自动机 简要说明 \(AC\) 自动机,全称 \(Aho-Corasick\ automaton\) ,是一种有限状态自动机,应用于多模式串匹配.在 \(OI\) 中通常搭配 \(dp\) 食用.因为它是状态自动机. 感性理解:在 \(Trie\) 树上加上 \(fail\) 指针.具体的讲解可以去看dalao们的博客(因为我实在是太菜了讲不好). 题目 Keywords Search 题目:给若干个模式串,再给一个文本串,问有几个模式串在文本串中出现过. 板子题.注意一个模式串只被计算一次

【bzoj3172】: [Tjoi2013]单词 字符串-AC自动机

[bzoj3172]: [Tjoi2013]单词 先用所有单词构造一个AC自动机 题目要求的是每个单词在这个AC自动机里匹配到的次数 每次insert一个单词的时候把路径上的cnt++ 那么点p->cnt就是以root到p这条路径为前缀的单词的个数 如果p->fail指向了点q,那么就会对q点产生p->cnt的贡献(root到q一定为root到p的后缀) 最后递推统计完所有fail的贡献,找到关键点输出就可以了 1 /* http://www.cnblogs.com/karl07/ */

ZOJ 3430 AC自动机

http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=4114 Nobita did use an outstanding anti-virus software, however, for some strange reason, this software did not check email attachments. Now Nobita decide to detect viruses in emails by himse