• 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;
    }
    View Code

     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;
    }
    View Code

    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
    ", ++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
    ",i,i/(i-nxt[i]));    
            printf("
    ");            
        }
        return 0;
    }
    View Code

    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
    ",lop-n%lop);
        }
        return 0;
    }
    View Code

    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;
    }
    View Code

    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]=='') {ok=1; continue;}
            if (!ok) insert(s);
            else printf("%d
    ",search(s));
        }
        return 0;
    } 
    View Code

    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
    ",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;
    } 
    View Code

     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;
    } 
    View Code

    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
    ",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;
    } 
    View Code

    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;
     } 
    View Code

    洛谷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
    ",Max);
            for (int i=1;i<=n;i++)
                if (ans[i]==Max) printf("%s
    ",s[i]);
        }
        return 0;
     } 
    View Code

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

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

  • 相关阅读:
    vite启用host代理,自动无限刷新问题
    机器人语音交互
    让or使用索引
    leetcode 77. Combinations 组合(中等)
    leetcode 257. Binary Tree Paths 二叉树的所有路径(简单)
    leetcode 934. Shortest Bridge 最短的桥(中等)
    为什么 SQL 语句使用了索引,但却还是慢查询?
    leetcode 47. Permutations II 全排列 II(中等)
    leetcode 79. Word Search 单词搜索
    leetcode 126. Word Ladder II 单词接龙 II(困难)
  • 原文地址:https://www.cnblogs.com/clno1/p/10986068.html
Copyright © 2020-2023  润新知