問題鏈接: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程序如下:
-
-
-
#include <iostream>
-
#include <string>
-
#include <map>
-
-
using namespace std;
-
-
#define getHashval(n, l) hv[n] - hv[n+l] * nbase[l]
-
-
typedef unsigned long long ULL;
-
-
const int base = 131;
-
ULL hv[100000+1];
-
ULL nbase[100000+1];
-
-
map<ULL, int> hashmap;
-
-
int main()
-
{
-
int m, l, count;
-
string s;
-
-
nbase[0] = 1;
-
for(int i=1; i<=100000; i++)
-
nbase[i] = nbase[i-1] * base;
-
-
-
while (scanf("%d%d", &m, &l) != EOF) {
-
-
cin >> s;
-
-
-
count = 0;
-
-
-
int len = (int)s.length();
-
hv[len] = 0;
-
for(int i=len-1; i>=0; i--)
-
hv[i] = hv[i+1] * base + s.at(i);
-
-
-
int end = len-m*l;
-
for(int i=0; i<l && i<=end; i++) {
-
-
hashmap.clear();
-
-
-
-
for(int j=i; j<i+m*l; j+=l)
-
hashmap[getHashval(j, l)]++;
-
if((int)hashmap.size() == m)
-
count++;
-
-
-
-
int end2 = len-m*l-l;
-
for (int j=i; j<=end2; j+=l) {
-
ULL temp = getHashval(j, l);
-
hashmap[temp]--;
-
if(hashmap[temp] == 0)
-
hashmap.erase(temp);
-
-
hashmap[getHashval(j+m*l, l)]++;
-
if((int)hashmap.size() == m)
-
count++;
-
}
-
}
-
-
-
cout << count << endl;
-
}
-
-
return 0;
-
}