zoukankan      html  css  js  c++  java
  • HASH

    $$Hash$$

    【引言】:

    由于(yjk)写了(Hash)博客,我才想起来我还有个(Hash)没写完,真是太尴尬了。


    (about)】:

    也就是我们什么时候会用到(Hash) , 和用(Hash)的重大好处 , 及其缺点

    对于哈希来说。当我们遇到字符串匹配的类型(当然,(KMP)大法更好)和其他有关于字符串之间比较的问题这类的问题, 我们往往用哈希解决

    哈希可以在大约在线性的复杂度内可以找到答案, 也是比较方便的, 挺快的对吧。

    问题也随之而来, 我们无法保证哈希的正确性是百分之百, 一定会有一定的错误概率, 后文将予以说明


    【工作原理】:

    哈希是怎么运行的呢? 其实也是很简单。

    我们可以看一下下面这一个图,

    我们首先可以有这么一个思路, 也就是我们这个字符串转化成数字,然后把他看成一个 (p)进制位数 , 我们在这里假设母串中(A ,B ,C , D ,……Z) 我们认为他们对应着(1 , 2 , 3,4…… 26) ,对应多少都一样, 这里是为了简洁, 我们认为他是(p)进制数 , 那么(ABC)也就是 ,$ p ^ 3 imes 1 + p ^2 imes 2 + p ^ 1 imes 3$ , 那么在这时候我们去匹配的时候 , 发现很神奇的事情, 那就是,只要这个P是个质数才能满足我们所说的正确性, 如果(p)不是质数,那么他一定可以被(a)整除, 那么 (p^1 就是 a^k)次方, 或者是(p ^1 = a imes k) ,这样的话, 不是(p)进制数, 同样的, 不同字符串出现相同的概率就会增加


    为什么会出现相同的哈希值(我们把这个东西称为hash值)呢? ,很简单 ,$5 ^ 1 imes 4 = 20 = 5^0 imes 20 $ ,我们这东西是不进位的, 所以会出现相同的数字 。 其正确性不保证,但是我们可以用两个模数来进行比较,那么这样的概率就会大大增加, 其中不必对其去模 ,(2^{64}) 让其自然溢出 , 就相当于去模了。那么自然,敢在(Codeforces)写自然溢出的都是猛士 ,


    为了避免其错误出现的概率 , 我们采用双(hash) ,也就是设(sum_1 , sum_2) ,然后挨着比较即可,倒也没什么难得,但是不要以为这样就万事无忧了,认为是无错,你只要写(hash),就有错的可能性。只不过是大点还是小点的问题罢了。


    【题目】

    【描述】:给定一个字符串(A)和一个字符串(B),求(B)(A)中的出现次数。(A)(B)中的字符均为英语大写字母或小写字母。

    我们就可以将其转化成哈希值,比较一下 ,就好了。

    【code】:

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <cmath>
    #include <cstdlib>
    #include <queue>
    #include <stack>
    #include <map>
    #include <set>
    #define int unsigned long long 
    using namespace std ;
    const int maxn = 1e6 + 10;
    const int mod = 10007 ;
    const int p = 133 ;
    inline int read()
    {
    	int x = 0 , f = 1 ; char ch = getchar() ;
    	while(!isdigit(ch)) { if(ch == '-') f = - 1 ; ch = getchar() ; }
    	while( isdigit(ch)) { x = x * 10 + ch - '0' ; ch = getchar() ; }
    	return x * f ; 
    }
    char a[maxn] , b[maxn] ;
    int sum[maxn] ; 
    int s ;
    int power[maxn] ;
    int ans ; 
    int n , m ;
    void init() //处理子串和母串 
    {
    	//题目给定均为大写或者小写, 故不必进行大小写转化
    	n = strlen(a + 1) ;
    	m = strlen(b + 1) ;
    	power[0] = 1 ;
    	for(int i = 1 ; i < 1000000 ; i ++)
    	{
    		power[i] = power[i - 1] * p ;//处理 p的n的次方 
    		//cout<<power[i] <<endl ;
    	}
    	sum[0] = 0 ;
    	for(int i = 1 ; i <= n ; i++) //处理处母串 
    	{
    		sum[i] = sum[i - 1] * p + (unsigned)(a[i] - 'A' + 1) ;
    	}
    	for(int i = 1 ; i <= m ; i++)
    	{
    		s = s * p + (unsigned)(b[i] - 'A' + 1 ) ;
    	} 
    }
    signed main()
    {
    	cin >> a+1 >>b+1 ;
    	init() ; //预处理 
    	for(int i = 0 ; i <= n - m ; i ++)
    	{
    	    //sum中记录的是前缀,我们需要乘以一个 p的m次方
    	    //让他们对齐,手模一下更好理解
    		if(s == sum[i + m ] - sum[i] * power[m]) 
    		{
    			ans ++ ;
    		}
    	}
    	cout<<ans<<endl ; 
    	return 0 ;
    }
    

    【哈希表】:

    有关(Hash)表,类比一下存图时的链式前向星存图,我们其实很容易的发现和链式前向星的存法有很大的相似性。
    首先,说明一下 , 只有用到能够支持插入字符串的时候,才用到(hash)表,其他情况下是用不到的。

    当然,如果是支持插入字符串的时候,我们可以用自动机来完成这一道题。(hash)能够做的最大优势的一种题,就是,查找循环节。

    【工作流程】 :

    这就看一下代码吧,能理解。我去搜搜,工作流程,写起来很麻烦。
    (here).

    这里我用的双hash,其(x,y)分别为两个(hash)函数产生的(hash)

    【Code】:

    插入 :

    void insert(int x, int y) 
    {
        nxt[++tot] = head[x] ;
        head[x] = tot;
        en[tot] = y ;
    }
    

    查询 :

    bool query(int x, int y) 
    {
        for (int i = head[x] ; i ; i = nxt[i]) 
        {
            if (en[i] == y) 
            {
                return true ;
            }
        }
        return false ;
    }
    

    然后就行了,你就又行了。

    【应用】 :

    查找循环节

    【题意】 :给定一个字符串,多次询问其某个子串的最短循环节。
    可以发现对于字符串串 (s[l,r]),如果长度为 (x) 的前缀是 (s[l,r]) 的一个循环节,则必有 (len(s[l,r])|x)(s[l,r-x]=s[l+x,r])

    如果存在长度为 $y$ 的前缀是 $s$ 的循环节, $y$ 是 $x$ 的因数且 $x$ 是串长的因数( $x|y, len(s)|x$),则长度为 $x$ 的前缀必然是 $s$ 的循环节(感性理解一下)。 考虑筛出每个数的最大质因数, $O(log n)$ 分解质因数,然后从大到小试除,看余下的长度是否是循环节,如果是则更新答案.

    【Code】:

    /*
     by : Zmonarch
     知识点 : 哈希
      思路 : 1. 循环节的长度也必能被乘除 
              2. 显然,我们去其最小质因数分解更优,处理出质因数, 直接除枚举
    		  3.删除一个循环节, 其中其他的字符, 再乘上p^len , 应该与 
    */
    
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <cmath>
    #include <map>
    #include <queue>
    #include <stack>
    #define int unsigned long long
    using namespace std ;
    const int kmaxn = 5e5 + 10 ;
    const int kmaxm = 2e6 + 10 ;
    const int p = 1e3 + 7 ;
    const int mod = 13331 ;
    inline int read() 
    {
    	int x = 0 , f = 1 ; char ch = getchar() ;
    	while(!isdigit(ch)) { if(ch == '-') f = - 1 ; ch = getchar() ; } 
    	while( isdigit(ch)) { x = x * 10 + ch - '0' ; ch = getchar() ; }
    	return x * f ; 
    } 
    int n , ans , len ; 
    char s[kmaxn] ; 
    int power[kmaxn] , sum[kmaxn] , prime[kmaxn] , vis[kmaxn]; 
    void prepare() //先处理出 p的n次方和哈希值 
    {
    	memset(vis , false , sizeof(vis)) ; 
    	power[0] = 1 ;
    	for(int i = 1 ; i <= n ; i++)
    	{
    		power[i] = power[i - 1] * p ;
    	}
    	sum[0] = 0 ;
    	for(int i = 1 ; i <= n ; i++)
    	{
    		sum[i] = sum[i - 1] * p + ( s[i] -'A' + 1 ) ; 
    	}
    }
    void prim() //利用线性筛,筛出最小质因数 
    {
    	for(int i = 2 ; i <= n ; i++)
    	{
    		if(vis[i]) continue ;
    		for(int j = 1 ; i * j <= n ; j++)
    		{
    			int t = i * j ;
    			if(vis[t]) continue ;
    			vis[t] = true ; 
    			prime[t] = i ; 
    		}
    	} 
    }
    bool check(int l , int r , int k)
    {
    	if(sum[r - k] - sum[l - 1] * power[r - k - l + 1] == sum[r] - sum[l + k - 1] * power[r - k - l + 1])
    	{
    		return true ; 
    	}
    	else return false ; 
    }
    signed main()
    {
    	n = read() ;
    	cin >> s+1 ; 
    	prepare() ;	
    	prim() ;
    	int q = read() ;
    	for(int i = 1 ; i <= q ; i++)
    	{
    		int l = read() , r = read() ;
    		ans = len = r - l + 1 ;
    		while(len > 1) //不断进行循环,查找循环节
    		{
    			int k = ans / prime[len] ; //寻找循环节在什么地方 
    			len /= prime[len] ; 
    			if(check(l , r , k))
    			{
    				ans = k ;
    			}
    			
    		} 
    		printf("%d
    " , ans) ;
    	}
    	return 0 ;
    }
    

    查找回文子串 :

    必然是不如(manacher)跑的快。
    题目: 查找回文子串 ;
    []code]

    ull num[22000000],num2[22000010];
    ull find_hash(int l,int r)
    {
        if(l<=r)
        return num[r]-num[l-1]*_base[r-l+1];//顺序Hash
        return num2[r]-num2[l+1]*_base[l-r+1];//倒序Hash
    }
    
    int l=0,r=min(i-1,len-i);
    int len=0;
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(find_hash(i,i+mid)==find_hash(i,i-mid)) l=mid+1,len=mid;//要求顺序Hash与倒序Hash匹配
        else r=mid-1;
    }
    

    然后其他的忘了,想起来再补充。

    【卡掉他】:

    对于如何卡掉(hash),请看这篇博客
    ,我没有要当毒瘤的意愿,我选择不写。要是这篇博客说了,(hash)好,回头就卡你(hash),莫不是不太好。

  • 相关阅读:
    hdu1042-N!-(java大数)
    拓扑排序-有向无环图(DAG, Directed Acyclic Graph)
    deleted
    deleted
    deleted
    deleted
    deleted
    deleted
    deleted
    deleted
  • 原文地址:https://www.cnblogs.com/Zmonarch/p/14003556.html
Copyright © 2011-2022 走看看