多项式插值取模哈希标记法

 

多项式插值取模哈希标记法是用来标记字符串的一种哈希标记法,能够快速,较为精确的进行哈希标记。

在该方法中,字符串被看做是一种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;
}

 

posted @ 2012-08-30 16:15  Accept  阅读(1456)  评论(0编辑  收藏  举报