zoukankan      html  css  js  c++  java
  • 多项式插值取模哈希标记法

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

    在该方法中,字符串被看做是一种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;
    }
  • 相关阅读:
    Centos 7 zabbix 实战应用
    Centos7 Zabbix添加主机、图形、触发器
    Centos7 Zabbix监控部署
    Centos7 Ntp 时间服务器
    Linux 150命令之查看文件及内容处理命令 cat tac less head tail cut
    Kickstart 安装centos7
    Centos7与Centos6的区别
    Linux 150命令之 文件和目录操作命令 chattr lsattr find
    Linux 发展史与vm安装linux centos 6.9
    Linux介绍
  • 原文地址:https://www.cnblogs.com/ka200812/p/2663997.html
Copyright © 2011-2022 走看看