字符串hash

来源:互联网 发布:tomcat怎么配置数据库 编辑:程序博客网 时间:2024/06/06 06:44
//HDU4622#include<iostream>#include<cstring>#include<algorithm>#include<vector>#include<set>#include<cmath>using namespace std ;//哈希函数的模 const int HASH = 10007 ;const int maxn = 2010 ;//哈希表结构 struct HASHMap{     //head使用HASH大小因为head开头是已经取摸HASH的数    //size扩展next     int head[HASH] , next[maxn] , size ;    //next[i]对应存放的数字为state     unsigned long long state[maxn] ;    //f[i]存储state[i]出现的最大的开始位置    int f[maxn] ;    void init(){        size = 0 ;        memset(head , -1 , sizeof( head )) ;    }     int insert( unsigned long long val , int id){        //哈希函数,h为val应该放的位置         int h = val%HASH ;        for ( int i = head[h] ; i != -1 ;i = next[i] )            //如果val之前存在,返回之前val出现的起始位置,更新当前为最大的位置            //因为要保证val相等,必须字符的长度要想等,所以在main函数枚举的时候要长度在外循环            //所以相同字符串一定长度相同,在给定的长度L下,他们在第二个循环的时候就已经全部找出,            //则一旦相同则当前的id值一定比之前的大,所以f[i] = id              if( val == state[i]){                int temp = f[i] ;                f[i] = id ;                return temp ;            }        //否则建立新节点,返回下标0是一个无用的下标         f[size] = id ;        state[size] = val ;        next[size] = head[h] ;        head[h] = size ++ ;        return 0 ;    } } H ;//将字符串转换成seed进制的数 const int seed = 13331 ;//p表示位权 unsigned long long p[maxn] ;//s表示字符串str的前缀的数值unsigned long long s[maxn] ;char str[maxn] ;int ans[maxn][maxn] ;int main(){    //求seed进制的位权p     p[0] = 1 ;    for(int i = 1 ;i<maxn ;i++)        p[i] = p[i-1] * seed ;    int T;    scanf("%d" , & T) ;    while( T--) {        scanf("%s" , str) ;        int n = strlen (str) ;        //将字符串转换成seed进制的整数         //求str前缀对应的         s[0] = 0 ;        for(int i = 1 ; i <=n ; i ++)            s[i] = s[i-1] * seed + str[i-1] ;        memset( ans , 0 , sizeof( ans )) ;        //枚举字符子串的长         //ans 表示i起点L长的子串, +1 表示存在一个i起点L长的子串, -1 表示有重复,-1之后能够保证整个字符串该子串只有一个         for(int L = 1 ; L <= n ;L ++){            H.init() ;            for(int i = 1 ; i+L -1 <= n ;i++){                //[i , i+L-1} 区间的字符子串的哈希值大小为  s[i+L - 1] - s [i]*p[L]                // s [i]*p[L] 这个相当于把s[i] 增大p[L] 倍                //就比如说十进制字符串" 123 "                 //字符子串" 23 " 的大小是 2*10 + 3                 //如果使用 s[i]*10 + s[i+1] 这样的连续处理虽然可以但是复杂度太高                //要在线性获得23 , 我们就要预处理前缀字符串大小1 12 123                //2到3位的字符串大小为 123 - 1* 100 = 23 ,                //使用的公式就是 s[i+L-1] - s[i-1]* p[L]   L 代表字符串的长度2,                 int l = H.insert( s[i+L-1] - s[i-1]* p[L] , i) ;                // +1 表示为获得一个子串                  ans[i][i+L -1 ] ++ ;                //-1表示重复了一个子串,但是因为每次都有ans[i][i+L-1]++ , 所以总体ans的和就是整个字符串不重复子串的个数                 ans[l][i+L-1] -- ;            }        }        //对于字符串str、        //他的[i,...n] 也就是从i开始的后缀的所有不相同的子串的个数就是ans从i开始到n结束的整个矩形所有数值的和        //对于字符串aab举例        //他的ans如下        /*        0 -1 -1 -3        0 1 0 1        0 0 1 1        0 0 0 1         */        //我们可以验证之前的结论        //那么要获得任意i,j区间范围的不同字符串的个数我们就要用下面的递推        //要求任意i,j区间的不同子串的个数 target[i][j] ,实际上就是求ans的i行j列的左下角和边界所有数值的和        //由递推公式有  ans[i][j]  + ans[i+1][j] + ans[i][j-1] - ans[i+1][j-1] ;        for( int i = n ;i>= 1 ;i--){            //i<=j 因为是(i,j) 范围字符串             for(int j = i ;j<=n ;j++){                //(i,j)不重复子串的个数计算公式                 ans[i][j]  += ans[i+1][j] + ans[i][j-1] - ans[i+1][j-1] ;            }        }        int m , u , v ;        scanf("%d" , & m ) ;        while( m-- ){            scanf("%d%d" , & u , & v) ;            printf("%d\n" , ans[u][v]) ;        }    }     return 0 ;}