1. 程式人生 > >Hash——字符串匹配(求s1在s2中出現的次數)

Hash——字符串匹配(求s1在s2中出現的次數)

sin 什麽 十進制 復雜 優化 style col 進制數 fin

題目描述:

這是一道模板題。

給定一個字符串 A 和一個字符串 B ,求 B A 中的出現次數。AB中的字符均為英語大寫字母。

求AB 中出現了幾次。(可重疊)

樣例輸入:

3

BAPC

BAPC

AZA

AZAZAZA

VERDI

AVERDXIVYERDIAN

樣例輸出:

1

3

0

首先要知道什麽是字符串hash(滾動哈希):

  單哈希可以O(m)的時間計算長度為m的字符串的哈希值,但對於本題,總的時間復雜度沒有改觀。時間會爆。

  這時我們就需要一個叫做滾動哈希的優化技巧。

  我們選取兩個合適的互質常數b和h(b<h),假設字符串C=c

1c2……cm,那麽我們定義哈希函數:H(C)=(c1bm-1+c2bm-2+……+cmb0) mod h 。

  正常數字是十進制的,這裏b是基數,相當於把字符串看做是b進制數。

  這一過程是遞推計算的,設H(C,k)為前k個字符構成的字符串的哈希值,則:(以下均不考慮取模的情況)

  H(C,k+1)=H(C,k)× b + ck+1

  字符串哈希,通常題目要求的是判斷主串的一段字符串與另一個匹配串是否匹配,即判斷字符C=c1c2……cm從位置k+1開始的長度為n的子串C‘=ck+1ck+2……ck+n

的哈希值與另一匹配串S=s1s2……sn的哈希值是否相等,則:

  H(C‘)=H(C,k+n) - H(C,k) × bn

  於是我們只要預求得b,就能在O(1)時間內得到任意字符串的字符串的子串哈希值,從而完成字符串匹配,那麽上述字符串匹配問題的算法復雜度就為O(n+m)。

  在實現算法時,可以利用32位或64位無符號整數計算hash值(如:unsigned long long),並取h=232或h=264,通過子讓溢出省去取模運算。

                                                                              ——By《一本通》


那麽本題就可以用上述方式AC了(書上代碼有bug,需自己改動)

  代碼如下:

#include<cstdio>
#include<cstring>
using namespace std;
#define ULL unsigned long long
#define K 103
int N;
char s1[1000005], s2[1000005];
ULL f[1000005],l1,l2,t;
ULL a[1000005];
ULL get(int x,int y)
{
    return f[y]-f[x-1]*a[y-x+1];
}
int main()
{
    //freopen("字符串匹配(求s1在s2中出現的次數).in","r",stdin);
    //freopen("字符串匹配(求s1在s2中出現的次數).out","w",stdout);    
    scanf("%d",&N);
    a[0]=1;
    for(int i=1;i<=1000000;++i)//預處理出a^n
        a[i]=a[i-1]*K;
    for(int i=1;i<=N;++i)
    {
        int ans(0);t=0;
        scanf("%s%s",s2+1,s1+1);
        l1=strlen(s1+1);l2=strlen(s2+1);
        for(int j=1;j<=l1;++j)
            f[j]=f[j-1]*K+(s1[j]-A);//計算主串的滾動哈希值 
        for(int j=1;j<=l2;++j)
            t=t*K+(s2[j]-A);//計算匹配串的哈希值 
        for (int j=1;j+l2-1<=l1;++j)
        {
            if(get(j,j+l2-1)==t)//枚舉起點為i,長度為n的子串,判斷與匹配串是否匹配 
                ans++;
        }
        printf("%d\n",ans);//輸出 
    }
    return 0;
}

Hash——字符串匹配(求s1在s2中出現的次數)