• 字符串知识清单


    之前一直跳过了字符串,现在才开始系统地学习,感觉需要记得模板挺多,在这里列个知识清单总结一下。

    (1)字符串Hash

      就是把字符串s视为一个B(一般B取不太大的质数)进制的数,用一个数组a来存s的前缀hash值,a用unsigned long long自动溢出比较方便。

      一个重要的柿子:hash(s[l~r])=hash(s[r])-hash(s[l-1])*B^(r-l+1)

      B的幂要预处理出来。

    (2)Manacher

      很NB的算法,要花点时间理解。

    #include<iostream>
    #include<cstring>
    #include<vector>
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    int Manacher(string &s){
        string t="$#";
        for(int i=0;i<s.size();++i){
            t+=s[i];
            t+="#";
        }
        vector<int> p(t.size(), 0);
        int Mmid=0,Mlen=0,mx=0,id=0;
        for(int i=1;i<t.size();++i){
            p[i]=mx>i?min(p[id*2-i],mx-i):1;
            while(t[i+p[i]]==t[i-p[i]]) ++p[i];
            if(i+p[i]>mx){
                mx=i+p[i];
                id=i;
            }
            if(p[i]>Mlen){
                Mlen=p[i];
                Mmid=i;
            }
        }
        return Mlen-1;
        //return s.substr((Mmid-Mlen)>>1,Mlen-1);
    }
    int main(){
        int cas=0;
        string s;
        while(1){
            cin>>s;
            if(s=="END") break;
            printf("Case %d: %d
    ",++cas,Manacher(s));
        }
        return 0;
    }
    View Code

    (3)KMP

      这个算法我起码学了3遍,还是经常忘。。。

      这份代码s下标从1开始。

    #include<cstring>
    #include<cstdio>
    #include<iostream>
    using namespace std;
    const int N=1e6+5;
    char A[N],B[N];
    int la,lb,nxt[N];
    void getnxt(){
        nxt[1]=0;
        for(int i=2,j=0;i<=la;++i){
            while(j&&A[i]^A[j+1]) j=nxt[j];
            if(A[i]==A[j+1]) ++j;
            nxt[i]=j;
        }
    }
    int kmp(){
        int ans=0;
        for(int i=1,j=0;i<=lb;++i){
            while(j&&B[i]^A[j+1]) j=nxt[j];
            if(B[i]==A[j+1]) ++j;
            if(j==la) ++ans,j=nxt[j];
        }
        return ans;
    }
    int t;
    int main(){    
        cin>>t;
        while(t--){
            scanf("%s %s",A+1,B+1);
            la=strlen(A+1),lb=strlen(B+1);
            getnxt();
            printf("%d
    ",kmp());
        }
        return 0;
    }
    View Code

    (4)最小表示法

      某些题目把形如“abcd”,“bcda”,"cdab","dabc"这些字符串(其实它们叫循环同构串)视为同一字符串,这时我们可以通过求出这些串的最小表示来提高比较和hash的效率。

         这份模板代码s下标从1开始。

         可以求出s的最小表示的起始位置。

    int minpre(char *s){
        int n=strlen(s+1);
        for(int i=1;i<=n;++i) s[n+i]=s[i];
        int i=1,j=2,k;
        while(i<=n && j<=n){
            for(k=0;k<n&&s[i+k]==s[j+k];++k) ;
            if(k==n) break; //s只由一个字符构成
            if(s[i+k]>s[j+k]){
                i=i+k+1;
                if(i==j) ++i;
            } 
            else{
                j=j+k+1;
                if(i==j) ++j;
            } 
        } 
        return min(i,j);
    }
    View Code

      给一道题目:POJ-3349

      此题POJ上提交时记得选G++。

    #include<cstdio>
    #include<cctype>
    #include<cstring> 
    #include<algorithm>
    using namespace std;
    const int N=1e5+5,P=99991;
    inline int read(){
        int x=0,w=0;char ch=0;
        while(!isdigit(ch)) w|=ch=='-',ch=getchar();
        while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
        return w?-x:x;
    } 
    int n,tot,tmp[12],snow[N][12],tail[N],last[N],minl[N];
    int minprs(int *a){
        int i=0,j=1,k;
        while(i<6&&j<6){
            for(k=0;k<6&&a[i+k]==a[j+k];++k) ;
            if(k==6) break;
            if(a[i+k]>a[j+k]){
                i=i+k+1;
                if(i==j) ++i;
            }
            else{
                j=j+k+1;
                if(i==j) ++j;
            }
        }
        return min(i,j);
    }
    bool cmp(int pa,int *a,int *b){
        int pb,pc,c[12];
        for(int i=0;i<12;++i) c[11-i]=b[i];
        pb=minprs(b),pc=minprs(c);
        bool ok=true;
        for(int i=0;i<6;++i) if(a[pa+i]^b[pb+i]) ok=false;
        if(ok) return true;
        ok=true;
        for(int i=0;i<6;++i) if(a[pa+i]^c[pc+i]) ok=false;
        if(ok) return true;
        return false;
    }
    int Hash(int *a){
        int sum=0;
        for(int i=0;i<6;++i){
            sum=(sum+a[i])%P;
            //mul=(long long)mul*a[i]%P;
        }
        return sum;
    } 
    bool insert(int *a){
        int val=Hash(a);
        for(int p=tail[val];p;p=last[p])
            if(cmp(minl[p],snow[p],a)) return true;
        ++tot;
        for(int i=0;i<12;++i) snow[tot][i]=a[i];
        minl[tot]=minprs(snow[tot]);
        last[tot]=tail[val];
        tail[val]=tot;
        return false;
    }
    int main(){
        n=read();
        for(int i=1;i<=n;++i){
            for(int j=0;j<6;++j) tmp[j]=tmp[j+6]=read();
            if(insert(tmp)){
                puts("Twin snowflakes found.");
                return 0;
            }
        }
        puts("No two snowflakes are alike.");
        return 0;
    }
    View Code

    (5)最小循环元

      直接上结论:

      ①如果i%(i-next[i])==0,那么(i-next[i])是s[1~i]的最小循环元长度.

      ②一个字符串的所有循环元长度都是最小循环元长度的倍数.

      ③把一个任意的s在添加字符最少的情况下补成一个循环串s',设s的长度为len,那么有两种情况:

       (i)next[i]=0  此时s自成s'的最小循环元,即需添加len个字符才能补成循环串.

       (ii)next[i]≠0 此时s’的最小循环元长度为t=(len-next[len]),若(len%t==0),s就等于s',否则最少需添加(t-len%t)个字符才能补成循环串.

    (6)Trie

      这可能是最好理解的字符串算法之一了。

      注意开数组时节点数应为(串的个数*串的最长长度)。

      来个一般的模板:(HDU-1671

    #include<cstdio>
    #include<cctype>
    #include<cstring>
    #include<iostream>
    using namespace std;
    const int N=1e5+5;
    inline int read(){
        int x=0,w=0;char ch=0;
        while(!isdigit(ch)) w|=ch=='-',ch=getchar();
        while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
        return w?-x:x;
    }
    struct Trie{
        int tot; 
        bool mark[N];
        int node[N][15];
        void reset(){
            tot=0;
            memset(mark,0,sizeof mark);
            memset(node,0,sizeof node);
        }
        bool insert(char *s){
            int p=0;
            int l=strlen(s);
            for(int i=0;i<l;++i){
                int num=s[i]^48;
                if(!node[p][num]) node[p][num]=++tot;
                p=node[p][num];
                if(mark[p]) return false;
            }
            mark[p]=true;
            for(int i=0;i<=9;++i) if(node[p][i]) return false;
            return true;
        }
    }T;
    int t,n;
    char s[10005];
    int main(){
        t=read();
        while(t--){
            T.reset();
            bool fg=0;
            n=read();
            for(int i=1;i<=n;++i){
                scanf("%s",s);
                if(fg) continue;
                if(T.insert(s)) continue;
                else
                    fg=1;
            }
            if(fg) puts("NO");
            else puts("YES");
        }
        return 0;
    }
    View Code

      稍稍扩展一下,可以用Trie来搞异或和的问题。

      看这道题:POJ-3764

      建一棵以0为根的树,用d[i]表示从根节点到节点i路径上的异或和,可以知道对于任意两个节点i和j,它们之间路径的异或和就等于d[i]^d[j]。

      那么问题转化成:在数组d中找两个数,使它们的异或值最大。

      这可以用Trie解决。具体来说,把每个数视为32位的二进制串(不足在高位补0),建一棵01Trie树,然后对于每个数,在Trie树中尽量走与它的每一位相反的边(具体看代码)。

      要注意高位补0是如何实现的,还有节点数应是(数的个数*32)。

    #include<cstdio>
    #include<cctype>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    const int N=1e5+5;
    inline int read(){
        int x=0,w=0;char ch=0;
        while(!isdigit(ch)) w|=ch=='-',ch=getchar();
        while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
        return w?-x:x;
    } 
    struct Graph{
        struct Edge{
            int v,w,last;
        }e[N<<1];
        int tot,tail[N];
        void clear(){
            tot=0;
            memset(tail,0,sizeof tail);
        }
        void add(int x,int y,int z){
            e[++tot]=(Edge){y,z,tail[x]};
            tail[x]=tot;
        }
    }G;
    int n,d[N];
    void dfs(int x,int pre){
        for(int p=G.tail[x];p;p=G.e[p].last){
            int v=G.e[p].v,w=G.e[p].w;
            if(v^pre){
                d[v]^=d[x]^w;
                dfs(v,x);    
            }
        }
    }
    int tree[N<<5][2],tot;
    void insert(int x){
        int p=1;
        for(int i=30;i>=0;--i){
            int ch=(x>>i)&1;
            if(!tree[p][ch]){
                tree[p][ch]=++tot;
            }
            p=tree[p][ch];
        }
    }
    int search(int x){
        int p=1,ans=0;
        for(int i=30;i>=0;--i){
            int ch=(x>>i)&1;
            if(tree[p][ch^1]) p=tree[p][ch^1],ans|=(1<<i);
            else p=tree[p][ch];
        }
        return ans;
    }
    int main(){
        while(scanf("%d",&n)!=EOF){
            int ans=0;
            G.clear();tot=1;
            memset(d,0,sizeof d);
            memset(tree,0,sizeof tree);
            for(int i=1;i<n;++i){
                int x=read(),y=read(),z=read();
                G.add(x,y,z),G.add(y,x,z);
            }
            dfs(0,-1);
            for(int i=0;i<n;++i){
                ans=max(ans,search(d[i]));
                insert(d[i]);
            }
            printf("%d
    ",ans);
        }
        return 0;
    }
    View Code

      再扩展一下,可以解决经典的哈夫曼编码问题,在此就不说了(这个人太懒了)。

    (7)AC自动机

      “至此,AC自动机终于揭开了“自动AC题目”的神秘面纱,向世人显露真容(滑稽)”

      与我以往想象的不同,其实AC自动机非常好理解也非常好写。

      挂一道几乎是模板题的代码:HDU-3695

    #include<cstdio>
    #include<cstring>
    #include<queue> 
    #include<iostream>
    using namespace std;
    const int N=255*1005;
    
    namespace AC{
        //根节点是0 
        int mark[N];
        int tree[N][26],fail[N],tot;
        void clear(){
            tot=0;
            memset(mark,0,sizeof mark);
            memset(fail,0,sizeof fail);
            memset(tree,0,sizeof tree);
        }
        void insert(string s){
            int p=0;
            for(int i=0;i<s.size();++i){
                int ch=s[i]-'A';
                if(!tree[p][ch]) tree[p][ch]=++tot;
                p=tree[p][ch];
            } 
            ++mark[p];
        }
        void getfail(){
            queue<int> q;
            for(int i=0;i<26;++i)if(tree[0][i]){
                fail[tree[0][i]]=0;
                q.push(tree[0][i]);
            }
            while(!q.empty()){
                int h=q.front();q.pop();
                for(int i=0;i<26;++i)
                    if(tree[h][i]){
                        fail[tree[h][i]]=tree[fail[h]][i];
                        q.push(tree[h][i]);
                    }
                    else tree[h][i]=tree[fail[h]][i];
            }
        }
        int query(string s){
            int ans=0,p=0;
            for(int i=0;i<s.size();++i){
                p=tree[p][s[i]-'A'];
                for(int j=p;j&&mark[j]^-1;j=fail[j]){
                    ans+=mark[j];
                    mark[j]=-1;
                }
            }
            return ans;
        }
    };
    void turn(string &s){
        string res;
        int i=0;
        while(i<s.size()){
            if(s[i]^'[') res+=s[i];
            else{
                int num=0;string tmp;
                while(s[i+1]>='0'&&s[i+1]<='9'){
                    num=num*10+(s[i+1]^48);
                    ++i;
                }
                while(s[i+1]^']'){
                    tmp+=s[i+1];
                    ++i;
                }
                ++i;
                while(num--) res+=tmp;
            }
            ++i;
        }
        s=res;
    }
    int n,t;
    string s,_s;
    int main(){
        ios::sync_with_stdio(false);
        cin>>t;
        while(t--){
            int ans=0;
            AC::clear();
            _s.clear();
            cin>>n;
            for(int i=1;i<=n;++i){
                cin>>s;
                AC::insert(s);
            }
            AC::getfail();
            cin>>s;turn(s);
            ans+=AC::query(s);
            for(int i=s.length()-1;i>=0;--i) _s+=s[i];
            ans+=AC::query(_s);
            cout<<ans<<endl;    
        } 
        return 0;
    }
    View Code

        还有一道题:洛谷P3796-AC自动机(加强版) 里面我加了点优化(lst数组),就是把fail指针跳跃的路径给压缩了,实测时间可减少四分之一。

    #include<cstdio>
    #include<queue>
    #include<cstring>
    #include<iostream>
    using namespace std;
    const int N=155,M=26;
    int n;
    string str,s[N];
    namespace AC{
        int tot=0,fail[N*70],lst[N*70],mark[N*70],cnt[N],tree[N*70][M];
        void clear(){
            tot=0;    
            memset(lst,0,sizeof lst);
            memset(cnt,0,sizeof cnt);
            memset(fail,0,sizeof fail);
            memset(tree,0,sizeof tree);
            memset(mark,0,sizeof mark);    
        }
        void insert(string &s,int id){
            int p=0;
            for(int i=0;i<s.length();++i){
                if(!tree[p][s[i]-'a']) tree[p][s[i]-'a']=++tot;
                p=tree[p][s[i]-'a'];
            }
            mark[p]=id;
        }
        void getfail(){
            queue<int> q;
            for(int i=0;i<M;++i)if(tree[0][i]) //别忘了if 
                lst[tree[0][i]]=fail[tree[0][i]]=0,q.push(tree[0][i]);
            while(!q.empty()){    
                int h=q.front();q.pop();
                for(int i=0;i<M;++i) if(!tree[h][i]) tree[h][i]=tree[fail[h]][i];
                else{
                    fail[tree[h][i]]=tree[fail[h]][i],
                    lst[tree[h][i]]=mark[fail[tree[h][i]]]?fail[tree[h][i]]:lst[fail[tree[h][i]]];
                    q.push(tree[h][i]);
                }
            }
        }
        void query(string &str){
            int maxl=0,p=0;
            for(int i=0;i<str.length();++i){
                p=tree[p][str[i]-'a'];
                for(int j=p;j&&~mark[j];j=lst[j]) ++cnt[mark[j]];
            }
            for(int i=1;i<=n;++i) maxl=max(maxl,cnt[i]);
            cout<<maxl<<endl;
            for(int i=1;i<=n;++i) if(cnt[i]==maxl) cout<<s[i]<<endl;
        }
    }
    int main(){
        while(cin>>n&&n){
            AC::clear();
            for(int i=1;i<=n;++i){
                cin>>s[i];
                AC::insert(s[i],i);
            }
            AC::getfail();
            cin>>str;
            AC::query(str);
        }
        return 0;
    }
    View Code

     (8)后缀数组

      还没学。。。留个坑。

  • 相关阅读:
    在linux上安装python, jupyter, 虚拟环境(virtualenv)以及 虚拟环境管理之virtualenvwraper
    linux
    Django ORM那些相关操作
    Django 中 form 介绍
    MySQL完整性约束
    git入门
    MySQL表的操作
    努力努力再努力
    Docker初始
    IO模型
  • 原文地址:https://www.cnblogs.com/gosick/p/10628482.html
Copyright © 2020-2023  润新知