zoukankan      html  css  js  c++  java
  • 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。洛谷题解上有几种优化的方式,我这里选择的是拓扑排序优化建图的方式。

  • 相关阅读:
    @bzoj
    @bzoj
    @codeforces
    @codeforces
    @bzoj
    @codeforces
    @codeforces
    @codeforces
    @NOIP2018
    反转字符串--C和Python
  • 原文地址:https://www.cnblogs.com/clno1/p/10986068.html
Copyright © 2011-2022 走看看