HDU4281

問題鏈接:HDU4821 String

字符串有關的算法,大致可以分爲三類。一是像本題一樣,用哈希函數來解(定長字符串);二是KMP算法(包括其變種);三是AC自動機。

這個問題,由於子串之間需要相互比較的組合太多,爲了避免重複的比較計算,需要找到一個有效的辦法進行處理。不然就組合爆炸了。所以,字符串的哈希函數是一個好的選擇。各個子串都計算一個哈希值,字符串比較問題就變成了哈希值比較的問題。進一步,把哈希值放入容器map中,就很快知道各個字串是否都不同(數一下數量)。m*l長的字符串,分爲m個l長的子串,各個子串的哈希值作爲key放入容器map中,如果容器map中有m個元素,說明各個字串都不相同。

有關字符串哈希值的計算,可以參見:B00013 字符串哈希函數。其中的內容來自百度百科,可惜編碼質量太差,不可以直接用的。

計算哈希函數有各種各樣的算法。本程序用的是BKDRHash算法,其中的基數一般取素數,以降低哈希值衝突的概率。這個基數,在實際計算時,可以看作是進制。

計算各個字符串的哈希值的方法也是本程序的一個亮點。這裏也是按照無名大神的做法做的。

先計算函數hv(),對於字符串s,若其長度爲n,則hv(n+1)=0,hv(i)=h(i+1)*base+第i個字符的ASCII值。這裏的base爲計算哈希值的基數。

再計算函數nbase(),該函數定義爲nbase(1)=1,nbase(i)=nbase(i-1)*base。

這樣,對於字符串s,第i個字符開始的長度爲l的各個子串的哈希值hash(i)=hv(i)-hv(i+l)*nbase(l)。

以上的哈希值計算方法,主要是爲了減少計算量。

同樣是爲了加快程序運行速度,程序中使用了一個帶參數的宏定義“#define getHashval(n, l) hv[n] - hv[n+l] * nbase[l]”,比起使用函數來要好一些,至少省去了程序調用返回和參數傳遞。這也是有經驗程序員的常見做法。

其他需要說明的,都在程序註釋裏了。

AC程序如下:

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. /* HDU4821 String */  
  2.   
  3. #include <iostream>  
  4. #include <string>  
  5. #include <map>  
  6.   
  7. using namespace std;  
  8.   
  9. #define getHashval(n, l) hv[n] - hv[n+l] * nbase[l]  
  10.   
  11. typedef unsigned long long ULL;  
  12.   
  13. const int base = 131;  
  14. ULL hv[100000+1];  
  15. ULL nbase[100000+1];  
  16.   
  17. map<ULL, int> hashmap; // 類型爲map,實際當作一個集合來使用  
  18.   
  19. int main()  
  20. {  
  21.     int m, l, count;  
  22.     string s;  
  23.   
  24.     nbase[0] = 1;  
  25.     for(int i=1; i<=100000; i++)  
  26.         nbase[i] = nbase[i-1] * base;  
  27.   
  28.     // 輸入m和l  
  29.     while (scanf("%d%d", &m, &l) != EOF) {  
  30.         //輸入字符串  
  31.         cin >> s;  
  32.   
  33.         // 計數清零  
  34.         count = 0;  
  35.   
  36.         // 計算長度爲l,各個字串的哈希值  
  37.         int len = (int)s.length();  
  38.         hv[len] = 0;  
  39.         for(int i=len-1; i>=0; i--)  
  40.             hv[i] = hv[i+1] * base + s.at(i);  
  41.   
  42.         // 窗口A,長度爲m*l,在輸入字符串上滑動,每次滑動1個字符,可以看到len-m*l+1個字串  
  43.         int end = len-m*l;  
  44.         for(int i=0; i<l && i<=end; i++) {  
  45.             //哈希值集合初始化:清空  
  46.             hashmap.clear();  
  47.   
  48.             // 將長度爲m*l的串,切割爲長度爲l的m個子串,分別將各個子串的哈希值放入hashmap集合  
  49.             // map中,map的key是子串的哈希值,map的值是一個計數,即相同哈希值出現n次的話則值爲n  
  50.             for(int j=i; j<i+m*l; j+=l)  
  51.                 hashmap[getHashval(j, l)]++;  
  52.             if((int)hashmap.size() == m)  
  53.                 count++;  
  54.   
  55.             // 窗口B,長度爲m*l,開始與窗口A重合,每次向右滑動l個字符,到窗口右邊與字符右邊對齊爲止  
  56.             // 每滑動一次B窗口,將最左邊那長度l子串的哈希值移出hashmap集合,右邊子串的哈希值添加到集合  
  57.             int end2 = len-m*l-l;  
  58.             for (int j=i; j<=end2; j+=l) {  
  59.                 ULL temp = getHashval(j, l);  
  60.                 hashmap[temp]--;  
  61.                 if(hashmap[temp] == 0)  
  62.                     hashmap.erase(temp);  
  63.   
  64.                 hashmap[getHashval(j+m*l, l)]++;  
  65.                 if((int)hashmap.size() == m)  
  66.                     count++;  
  67.             }  
  68.         }  
  69.   
  70.         // 輸出結果  
  71.         cout << count << endl;  
  72.     }  
  73.   
  74.     return 0;  
  75. }  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章