1. 程式人生 > >【暖*墟】 #字串Hash# 字串Hash與例題

【暖*墟】 #字串Hash# 字串Hash與例題

【字串Hash】

1.特徵與理解

用於尋找字元組出現的位置次數的問題,即【字串匹配問題】。

2.滾動雜湊的優化技巧

選取兩個合適的互質數b,h(b<h),假設字串C=c1c2...cm,

定義雜湊函式為:H(C)=(c1*b^(m-1)+c2*b^(m-2)+....+cm*b^0) mod h

b為基數,H(C)的處理相當於把字串看成b進位制數。

這一過程通過遞迴計算:H(C,k)=H(C,k-1)*b+ck

判斷某段字元與另一匹配串是否匹配,即判斷:

(某段字元:從位置k+1開始的長度為n的子串C'=ck+1ck+2....ck+n)

H(C')=H(C,k+n)-H(C,k)*b^n

與 H(S) 的關係。

---> 預處理字串所有字首Hash值。

---> 在O(1)時間內查詢它的任意子串Hash值。

對unsigned long long的使用:

可以用於取模2^64。遇到這種限制條件時就要想到用unsigned long long型別。

可以簡潔地宣告為typedef unsigned long long ull。因為ull的範圍是[0,2^64-1]。

( 2^64:18446744073709551616,即10^19。)

所以,如果ull型別的整數溢位了,就相當於取模2^64了。

而long long的範圍是[-2^63,2^63-1],因為有符號的第63位表示“正負”而不表示數值

3.例題與運用

(1) poj3461 Oulipo

#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
typedef unsigned long long ull;

/*【Oulipo】poj3461
給你兩個字串s1和s2,求出s1在s2中出現的次數。 */
 
//字串Hash的模板題

const int b=13331; //base,基數

ull power[1000009]; //儲存b^n
void init(){
    power[0]=1; //b^0=1
    for(int i=1;i<1000000;i++)
        power[i]=power[i-1]*b; //對ull取模
}

char s1[10009],s2[1000009];
ull sum[1000009]; //記錄主串雜湊值的陣列

int main(){
    init(); int T; cin>>T;
    while(T--){
        scanf("%s%s",s1+1,s2+1); //從第一位開始輸入
        int n=strlen(s1+1),m=strlen(s2+1);
        sum[0]=0; //記錄主串雜湊值的陣列
        for(int i=1;i<=m;i++) //計算主串的滾動Hash值
            sum[i]=sum[i-1]*b+(ull)(s2[i]-'A'+1);
        ull s=0;
        for(int i=1;i<=n;i++) //計算匹配串的Hash值
            s=s*b+(ull)(s1[i]-'A'+1); 
        int ans=0;
        for(int i=0;i<=m-n;i++)
            if(s==sum[i+n]-sum[i]*power[n]) ans++;
        printf("%d\n",ans);
    }
    return 0;
}

(2)poj2406 Power Strings

#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

/*【power strings】poj2406
給出一個不超過1e6的字串,求這個字串最多有多少個週期。 */
 
//字串Hash的模板題

const int b=131; //base,基數
const int mod=10009; //h,要mod的數
int len;
ull hashs[1001000];

ull cal(int x,ull y){ //y^x
    ull now=1;
    while(x){
        if(x&1) now=(now*y)%mod;
        x>>=1; y=(y*y)%mod;
    }
    return now;
}

bool check(int x){ //x:最小的迴圈長度
    ll cc=cal(x,(ull)b);
    for(int i=(x<<1);i<=len;i+=x)
        if((hashs[i]-(hashs[i-x]*cc)%mod+mod)%mod!=hashs[x]) 
        //求H(C,k+n)-H(C,k)*b^n,之後的每一段長度的hash值是否匹配
        //加上mod,防止相減為負值
            return false;
    return true;
}

int main(){
    while(1){
        char s[1001000];
        scanf("%s",s+1);
        len=strlen(s+1);
        if(len==1&&s[1]=='.') return 0;
        for(int i=1;i<=len;i++) //求出Hash值
            hashs[i]=(hashs[i-1]*b+s[i])%mod;
        for(int i=1;i<=len;i++) //列舉迴圈的長度
        //↓↓↓最小的迴圈長度對應最大的週期,即第一個滿足的就是最大週期數
            if(len%i==0 && check(i)){ 
                printf("%d\n",len/i); break; 
            }
    }
    return 0;
}

(3)poj2752 -hash

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
typedef unsigned long long UL;

UL hashs[400005],mul[400005],K=31,P=1000000031;
char s[400005];

int main(){
    mul[0]=1;
    for(int i=1;i<=400000;i++) //預處理k^n
        mul[i]=mul[i-1]*K%P;
    while(~scanf("%s", s+1)){
        int n=strlen(s+1);
        for(int i=1;i<=n;i++) //求hash字首和
            hashs[i]=(hashs[i-1]*K+s[i]-'a'+1)%P;
        for(int i=1;i<=n;i++)
            if(hashs[i]==((hashs[n]-(hashs[n-i]*mul[i]%P))+P)%P) //字首=字尾
                printf("%d ", i);
        putchar('\n');
    }
    return 0;
}

poj2752 -kmp

#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

/*【既是字首又是字尾的子串】POJ 2752
從大到小輸出所有既是字首又是字尾的字串的長度。  */

//該字首串的最後一個字元肯定與s的最後一個字元相同。
//從n - 1位既最後一位開始回滾,若s[next[n-1]] == s[n-1],
//則子串s[0,1,2,...,next[n-1]]是滿足條件的子串。
//然後判斷s[next[next[n-1]]] == s[n-1]是否成立,
//這樣一直回滾,直到next[next[.....next[n-1]]] == -1為止。
 
int Next[400005],ans[400005];
char str[400005];
int cnt,len;
 
void getNext(){
    Next[0] = -1; //起點
    int i = 0, j = -1;
    while (i < len){ //設定終點
        if (j == -1 || str[i] == str[j]){ 
        //↑↑↑找不到任何之前的匹配,但這一位可以匹配
            i++; j++; Next[i]=j; //給next賦值
        }
        else j = Next[j]; //回溯到上一個next尋找匹配
    }
}
 
int main(){
    while (scanf("%s", str) != EOF){
        len = strlen(str);
        getNext(); //預處理建立next陣列
        cnt = 0; //存答案的種類數
        int j = Next[len - 1]; //用next不斷向前滾動
        while (j != -1){ 
            if (str[j] == str[len - 1]) 
                ans[cnt++] = j + 1; //匹配串長度
            j = Next[j];
        }
        for (int i = cnt - 1; i >= 0; --i)
            printf("%d ", ans[i]); //從ans[0]開始記錄的
        printf("%d\n", len);
    }
    return 0;
}

(4)bzoj3916 friends

#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
#include <map>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

/*(bzoj3916)【friends】【hash】
有三個好朋友喜歡在一起玩遊戲,A君寫下一個字串S,
B君將其複製一遍得到T,C君在T的任意位置(包括首尾)插入一個字元得到U.
現在你得到了U,請你找出S.  */

const int P 131
const int N 2000010
ull h[N],t[N];
map<ull,int>q;
char ss[N];

int main(){
    int n,ans,len; scanf("%d",&n);
    if(n%2==0||n==1){ puts("NOT POSSIBLE"); return 0; }
    len=(n-1)/2; scanf("%s",ss+1);
    t[0]=1;h[0]=0;
    for(int i=1;i<=n;i++) t[i]=t[i-1]*P; //預處理P^n
    for(int i=1;i<=n;i++) h[i]=h[i-1]*P+(ull)ss[i]; //hash字首和
    for(int i=1;i<=n;i++){
        if(i<=len){
            ull l=h[i-1],mid=h[len+1]-t[len+1-i]*h[i],r=h[n]-h[n-len]*t[len];
            l=l*t[len-i+1]+mid;
            if(l==r){
                if(q[l]||(!ans)) ans=i,q[l]=1;
                else{ puts("NOT UNIQUE"); return 0; }
            }
        }
        if(i==len+1){
            ull l=h[i-1],r=h[n]-t[len]*h[i];
            if (l==r){
                if(q[l]||(!ans)) ans=i,q[l]=1;
                else{ puts("NOT UNIQUE"); return 0; }
            } 
        }
        if(i>len+1){
            ull l=h[len],mid=h[i-1]-h[len]*t[i-1-len],r=h[n]-h[i]*t[n-i];
            r=mid*t[n-i]+r; 
            if(l==r){
                if(q[l]||(!ans)) ans=i,q[l]=1;
                else{ puts("NOT UNIQUE"); return 0; }
            } 
        }
    }
    if(!ans){ puts("NOT POSSIBLE"); return 0; }
    else{ 
        int t=1;
        while(len--){
            if(t==ans) t=t+1;
            printf("%c",ss[t]);
            t++;
        }
    }
    return 0;
}