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


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

    在该方法中,字符串被看做是一种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;
    }
  • 相关阅读:
    Python3.x:定义一个类并且调用
    Spring编码过滤器:解决中文乱码
    Web.xml中自动扫描Spring的配置文件及resource时classpath*:与classpath:的区别
    你是否属于中等收入群体
    Python3.x:BeautifulSoup()解析网页内容出现乱码
    Activiti工作流引擎数据库表结构
    Java:出现错误提示(java.sql.SQLException:Value '0000-00-00' can not be represented as java.sql.Date)
    Django框架搭建(windows系统)
    Eclipse配置多个jdk
    Activiti:创建activiti工程
  • 原文地址:https://www.cnblogs.com/ka200812/p/2663997.html
Copyright © 2020-2023  润新知