多项式插值取模哈希标记法是用来标记字符串的一种哈希标记法,能够快速,较为精确的进行哈希标记。
在该方法中,字符串被看做是一种37进制的数。
对于一个字符串,可以用以下方法计算它的哈希值
LL p=0; //计算hash值用 for(j=0;j<len;j++) p=p*T+(a[j]-'a'+1);
其中T=37。
我们可以发现,如果一个串的长度上千上万,那么这个hash值就会很大。这时候,我们需要将hash值进行分类,也就是取模,我们设定一个常数,假设这个常数是H,那么所有的hash值将会被分类成H类。分别是
0+H,0+2*H......0+N*H
1+H,1+2*H,1+3*H.....1+N*H
...
...
...
N-1+H,N-1+2*H,N-1+3*H.....N-1+N*H
我们用一个vector<LL>val[H],即H个vector容器来储存着H类hash值。
假设现在某个字符串的hash值是M,那么我们查找的时候,就先将M%H,看看它到底属于哪个分类,然后再对val[M%H]进行遍历,看看里面是否有某一个值等于M,如果有,就说明该字符串曾经出现过了,如果没有的话,就将M插入到val[M%H]中。
看一个具体的练习:
给定N个字符串,N<=100,每个串的长度<=1000。
求有多少个子串,子串长度越长越好,在这N个字符串中最少出现了N/2+1次。
例如:
3
abcdefg
bcdefgh
cdefghi
那么答案是
bcdefg
cdefgh
分析:
我们可以在1到1000中二分枚举最终答案的长度,然后对给定的N个字符串,hash它们所有可能组成的长度为二分长度的子串,用一个计数方法,记录每种hash重复出现的次数(同一个串中重复出现不算)。最后把所有出现次数大于等于N/2+1的子串数出来就可以了。
View Code
#include<iostream> #include<string> #include<vector> #include<queue> using namespace std; #define maxn 111 #define maxlen 1111 #define MIC 334423 //分类 #define LL unsigned __int64 const LL T=37; //进制 struct node { char *s; //hash串的内容 LL pos; //hash值所属的分类 int cnt; //hash串出现的次数 int lastItm; //上一次得到该hash值的串的下标,避免从一个串中得到两个同样的子串 node(char* _s = NULL, LL _p = 0, int _c = 0, int _l = 0) : s(_s), pos(_p), cnt(_c), lastItm(_l) {} }; struct Hash { vector<LL>val[MIC]; //储存每个hash值 vector<int>index[MIC]; //储存每个hash值对应的node节点在que中的下标 vector<node>que; //储存node节点 void clear() { int i; for(i=0;i<int(que.size());i++) { val[que[i].pos].clear(); index[que[i].pos].clear(); } que.clear(); } void insert(char *s,LL p,int lt) //将hash值为p,串为s,母串下标为lt的hash串插入 { int i,pos=p%MIC; //获得该hash值所在的分类 for(i=0;i<int(val[pos].size());i++) //看该分类中是否已经存在该hash值了 { if(val[pos][i]==p) { if(lt!=que[index[pos][i]].lastItm) //如果存在,但是母串不同,数量加加,同时更新母串下标 { que[index[pos][i]].cnt++; que[index[pos][i]].lastItm=lt; } return; } } //不存在,将该hash值添加到pos类中 val[pos].push_back(p); index[pos].push_back(que.size()); //记录该hash串在que中的下标,即que.size()这个位置 que.push_back(node(s,pos,1,lt)); } int out(int lim,string *s,int len) { int i,ans=0,j; for(i=0;i<int(que.size());i++)//对所有的长度为len的hash串 { if(que[i].cnt>=lim) //如果它出现的次数大于等于lim,说明是答案 { s[ans]=""; for(j=0;j<len;j++) s[ans]+=que[i].s[j]; ans++; } } return ans; } }h; int n,l,r,mid; //二分使用 LL R[maxn]; //T进制中,向前推进某一位需要的阶数,calc函数中要用到 char a[maxn][maxlen]; //记录题目给出的字符串 string ptr[maxlen]; //答案记录在这里 inline int calc(int len) { h.clear(); //每次清零 int i,j,m; for(i=0;i<n;i++) { m=strlen(a[i]); if(m<len)continue; LL p=0; //计算hash值用 for(j=0;j<len;j++) p=p*T+(a[i][j]-'a'+1); h.insert(a[i],p,i); //将属于第i个母串的,hash值为p的,从i开始的hash串插入 for(j=len;j<m;j++) { p=p*T+(a[i][j]-'a'+1)-(a[i][j-len]-'a'+1)*R[len]; h.insert(a[i]+j-len+1,p,i); } } return h.out(n/2+1,ptr,len); } int main() { int i,flag=0,anslen; R[0]=1; for(i=1;i<maxn;i++) R[i]=R[i-1]*T; //freopen("D:\\in.txt","r",stdin); while(scanf("%d",&n)==1) { if(n==0) break; if(flag) printf("\n"); flag=1; for(i=0;i<n;i++) { scanf("%*c%s",a[i]); } l=1;r=maxlen; int ans=0; anslen=1; while(l<=r) { mid=(l+r)/2; if(calc(mid)) { anslen=mid; //最大长度更新 l=mid+1; } else { r=mid-1; } } ans=calc(anslen); //最大长度下的个数 if(ans==0) printf("?\n"); else { sort(ptr,ptr+ans); for(i=0;i<ans;i++) cout<<ptr[i]<<endl; } } return 0; }