字符串學習筆記

1、字符串哈希

定義

字符串哈希實質上就是把每一個不一樣的字符串轉成不一樣的整數
這樣相對於存儲整個字符串來講佔用的空間更少,並且也便於比較c++

實現

咱們能夠把每個字符想象成一個數字,而後確立一個進制\(bas\)
好比一個字符串\(abc\)
咱們能夠把它表示爲\((c-a+1)\times bas^{0} + (b-a+1)\times bas^{1} +(a-a+1)\times bas^{2}\)
這裏有幾個須要注意的地方
首先進制的選擇要大於字符的種類數,不然會有很大的機率出現衝突
還有就是咱們在把字符轉成整形的時候,能夠直接使用它的\(ASCII\)碼值,也能夠用它減去一個字符
可是在使用第二種方法的時候,減去一個字符後要加上一個\(1\),不然會出現錯誤
好比字符串\(aaa\)\(aa\),若是咱們將每個字符減去\(a\)後不把它加上\(1\)的話
最後兩個字符串的哈希值都會變成\(0\),也就是說會把這兩個字符串判成相等,會出現錯誤的結果
因爲字符串的長度可能很大,所以若是咱們一直把它的哈希值累加的話,頗有可能會溢出
所以,咱們要對某個字符串的哈希值取模,方法有兩種
一種是選取一個較大的質數
好比\(19260817\)\(19660813\)\(1222827239\)\(212370440130137957\)
另外一種是使用\(unsigned long long\)使其天然溢出
其實後一種方法就至關於對\(2^{64}-1\)取模
還有一種操做是取出字符串中某一段字符\([l,r]\)\(hash\)
這時咱們要用到一個公式\(ha[r]-ha[l-l]*pw[r-l+1]\)
其中\(ha[i]\)爲該字符串前\(i\)位的\(hash\)值,\(pw[i]\)爲進制\(bas\)\(i\)次方算法

代碼實現

咱們拿洛谷P3370來舉例子
這裏我用的是天然溢出數組

#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;
const int maxn=1e5+5;
ll f[maxn];
ll bas=233,cnt=0;
ll get_hash(char s[]){
    ll ans=0,len=strlen(s);
    for(ll i=0;i<len;i++){
        ans=ans*bas+s[i];
    }
    return ans;
}
char s[maxn];
int main(){
    int n;
    scanf("%d",&n);
    while(n--){
        scanf("%s",s);
        f[++cnt]=get_hash(s);
    }
    sort(f+1,f+1+cnt);
    int now=1;
    for(ll i=2;i<=cnt;i++){
        if(f[i]!=f[i-1]) now++;
    }
    printf("%d\n",now);
}

2、KMP字符串匹配

定義

\(KMP\)算法是一種改進的字符串匹配算法,由\(D.E.Knuth,J.H.Morris\)\(V.R.Pratt\)提出的,所以人們稱它爲克努特—莫里斯—普拉特操做(簡稱\(KMP\)算法)。\(KMP\)算法的核心是利用匹配失敗後的信息,儘可能減小模式串與主串的匹配次數以達到快速匹配的目的。具體實現就是經過一個\(next()\)函數實現,函數自己包含了模式串的局部匹配信息。\(KMP\)算法的時間複雜度\(O(m+n)\)
通俗的來講就是在須要匹配的那個串上給每一個位置一個失配指針\(fail[j]\),表示在當前位置j失配的時候須要返回到\(fail[j]\)位置繼續匹配,而這就是KMP算法優秀複雜度的核心。函數

實現

咱們設\(fail[i]\)爲第\(1\)-第\(i\)位中前綴與後綴相同的部分最長是多長。
這樣,便可以理解爲,若第\(i\)位失配了,則至少要往前跳多少步,纔可能從新匹配得上。
咱們拿實際的圖來演示一下

目前,咱們匹配到了\(i-1\)的位置,\(fail[i-1]=j\)
即圖中劃黃色線的部分徹底相同
咱們拿當前的\(fail[i-1]\)去繼續匹配
若是\(s[i]=s[j+1]\)那麼\(fail[i]\)更新爲\(j+1\)便可
若是\(s[i] \neq s[j+1]\)那麼若是按照暴力的思路,咱們會把\(j--\)繼續匹配
可是實際上,咱們能夠直接從\(fial[j]\)的位置開始匹配
由於圖中兩個藍色的部分徹底相等,而根據黃色的部分徹底相等
咱們又能夠知道從\(i-1\)開始也有一個藍色的部分和它相等
這時咱們只須要判斷\(s[i]\)\(s[fail[j]+1]\)的關係就能夠了
若是不存在,則繼續跳\(fail\)
易證當前必定是次優解spa

代碼實現

咱們拿洛谷P3375來舉例子指針

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
char s[maxn],s1[maxn];
int f[maxn];
int main(){
    scanf("%s%s",s+1,s1+1);
    int l=strlen(s+1);
    int l1=strlen(s1+1);
    for(int i=2,j=0;i<=l1;i++){
        while(j && s1[i]!=s1[j+1]) j=f[j];
        if(s1[i]==s1[j+1]) f[i]=++j;
    }
    for(int i=1,j=0;i<=l;i++){
        while(j && s[i]!=s1[j+1]) j=f[j];
        if (s[i]==s1[j+1]) j++;
        if(j==l1){
            printf("%d\n",i-l1+1);
            j=f[j];
        }
    }
    for(int i=1;i<=l1;i++){
        printf("%d ",f[i]);
    }
    printf("\n");
    return 0;
}

3、manacher算法

定義

馬拉車\((Manacher)\)算法是在\(O(n)\)時間內解決尋找源字符串的最長迴文子串\(S\)的問題的算法。code

實現

首先咱們要知道,迴文串分爲奇迴文串和偶迴文串
\(aaaa\)這樣的就是偶迴文串,而\(aba\)則是奇迴文串
不難發現,奇迴文串都有一個迴文中心,所以在查找時能夠由中心向兩邊擴展
可是偶迴文串則沒有這一個性質,所以查找起來不如奇迴文串方便
爲了使查找更方便,咱們可讓全部的偶迴文串都變成奇迴文串
操做實現也很簡單,就是將原字符串的首部和尾部以及每兩個字符之間插入一個特殊字符,這個字符是什麼不重要,不會影響最終的結果
同時還要在隊首以前再插入另外一種特殊字符,防止運算時越界
好比\(abaca\)擴展後變爲\(#*a*b*a*c*a*\)
在進行馬拉車算法時,咱們要維護一個已經肯定的右側最靠右的迴文串的右邊界\(r\)和迴文中心\(mids\)
同時定義一個數組\(f[i]\)爲以\(i\)爲中心的最大回文半徑
當咱們遍歷到\(i\)時,若是\(i\)在右邊界以內
那麼根據對稱性,有\(f[i]=f[s*mids-i]\)
同時,\(i\)所擴展的範圍必須在\(r\)以內,所以結果還要與\(r-i+1\)\(min\)
擴展完已知的區域,咱們再向兩邊擴展未知的區域
最後咱們更新\(mids\)\(r\)便可
最後的答案就是最大回文半徑減去一,手模一下便可blog

代碼實現

咱們拿洛谷P3805來舉例子字符串

#include<bits/stdc++.h>
using namespace std;
const int maxn=22e6+5;
char s1[maxn],s[maxn];
int f[maxn],ans,n,cnt;
int main(){
    scanf("%s",s1+1);
    n=strlen(s1+1);
    cnt=2*n+1;
    for(int i=1;i<=cnt;i++){
        if(i&1) s[i]='&';
        else s[i]=s1[i/2];
    }
    s[0]='%';
    for(int i=1,mids=0,r=0;i<=cnt;i++){
        if(i<=r) f[i]=min(f[2*mids-i],r-i+1);
        while(s[i+f[i]]==s[i-f[i]]) f[i]++;
        if(i+f[i]>r) r=i+f[i]-1,mids=i;
        if(f[i]>ans) ans=f[i];
    }
    printf("%d\n",ans-1);
    return 0;
}
相關文章
相關標籤/搜索