1. 程式人生 > >【總結】字符串知識清單

【總結】字符串知識清單

bst 所有 type 需要 dfs stream 要花 之間 event

之前一直跳過了字符串,現在才開始系統地學習,感覺需要記得模板挺多,在這裏列個知識清單總結一下。

(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\n",++cas,Manacher(s)); } return 0; }
View Code

(3)KMP

  這個算法我起碼學了3遍,還是經常忘。。。

  這份代碼s下標從1開始。

技術分享圖片
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1005;
int m,n,next[N];
char A[N],B[N];     
int main(){
    while(1){
        int ans=0;
        scanf("%s",A+1);
        m=strlen(A+1);
        if(m==1 && A[1]==#) break;
        scanf("%s",B+1);
        n=strlen(B+1);
        next[1]=0;
        for(int i=2,j=0;i<=n;++i){
            while(j>0 && B[i]^B[j+1]) j=next[j];
            if(B[i]==B[j+1]) ++j;
            next[i]=j;
        }
        for(int i=1,j=1;i<=m;++i){
            while(j>0 && (B[j+1]^A[i] || j==n)) j=next[j];
            if(B[j+1]==A[i]) ++j;
            if(j==n) ++ans,j=0;
        }
        printf("%d\n",ans); 
    }
    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-next[i])%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\n",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

(8)後綴數組

  還沒學。。。留個坑。

【總結】字符串知識清單