hash hash hash : ))

hash:

hash简述,大概就是把一个字符串映射到一个整数上(这个整数就是一个自定义进制(mode)的数),通过比较该整数匹配字符串,这样可以实现字符串之间的O(1)匹配.
为什么要按位处理,因为这样方便分离字串.
怎么映射?就是将每位(i)分离乘上\(mode^{(i-1)}\),得到的映射整型就是这个字符串的hash值.
因为这个数一般无比巨大(在我们的字符串长度较高的情况下),所以我们需要一个模数来限制其大小.
一般情况下,我们的hash值都是unsigned long long(这样int×int不会炸),前面说要有一个模数限制,这样是不会炸整型了,但是可以看出其缺点,我们可能遇到hash值相同但是不匹配的串,我们称之为hash冲突.
那么我们再想想模数的选择,应该是一个较大的质数(这样减少hash冲突).
虽说较大质数可以减少hash冲突,但还是会有冲突,怎么办呢?取两个模数,而且要是不常用的模数(因为常用的容易被定向卡).
当两个模数下的hash值均相等,那么我们认为字符串匹配.
当然,我们在用unsinged long long 类型时,因为\(2^{64}-1\)就是个质数,所以不写模数让它自然溢出在一些不卡hash冲突的题目中还是很实用的(主要咱码量小啊).
另外一个实用的技巧就是分离字串hash值,得到hash值时类似于前缀和的操作决定了分离字串hash值的近O(1)的时间复杂度(虽然常数较大),这个是十分实用的.
我们只需预处理出所有位数应该对应乘上的mode的次数(p数组)以及某个串的任意一位到初始位的hash值(ha数组),任意一个字串\([i,j]\)的hash值就可以用\(ha[j]-ha[i-1]×(pow[j-i+1])\)得到.
例题(板子):乌力波

#include<bits/stdc++.h>
#define llu unsigned long long
#define lll long long
using namespace std;
const int N=1e6+20;
const int base=131;//进制数应该是一个质数,131据说冲突可能性较小.
int n,h1,h2,ans;
char c1[N],c2[N];
llu p[N];//存储对应位乘数的大小 
llu ha[N];//存储查询的字符串的子串(从起点开始)hash值. 
llu cuthash(int l,int r)
{
	return ha[r]-p[r-l+1]*ha[l-1];
}
void init()
{
	cin>>n;
	p[0]=1;
	for(int i=1;i<=N-20;i++)p[i]=p[i-1]*base;
	while(n--)
	{
		ans=0;
		cin>>(c1+1)>>(c2+1);
		int l1=strlen(c1+1),l2=strlen(c2+1);
		llu hash1=0;//存储判断的那个字符串的hash值.
		for(int i=1;i<=l1;++i)
			hash1=hash1*base+c1[i];
		for(int i=1;i<=l2;++i)
			ha[i]=ha[i-1]*base+c2[i];
		for(int i=1;i<=l2-l1+1;++i)
		{
			llu k=cuthash(i,i+l1-1);//得到大小为l1字串的hash 
			if(k==hash1)
				++ans;
		} 
		cout<<ans<<'\n';
	}
}
int main()
{
	ios::sync_with_stdio(0);
   	cin.tie(0);
   	cout.tie(0);
	init();
	return 0;
}

另外就是一个双模数哈希的写法.
前文提及,自然溢出的hash明显不保险.
所以说就有了在自然溢出基础上去再加一个模数取hash值.
(当然,理论上来讲,只要这个模数别人不知道,就无法单向卡你的模数,所以貌似不带自然溢出的单模数hash也是可以的,可省时间).
那么简单叙述一下单模数hash的写法:首先要取一个较大的质数作为模数,其他的...好像没什么可讲的.
看代码吧.

llu ha[N],p[N];
//预处理每个位置上的乘数.
for(int i=1;i<=(1e5);++i)
{
	p[i]=p[i-1]*base%mod;
}
//取hash值,注意应该是long long类型,unsigned long long不行,不知道为什么有没有神犇给蒟蒻解释下...
ll gethash(int l,int r)
{
	return (ha[r]-ha[l-1]*p[r-l+1]%mod+mod)%mod;
}
//处理串s的所有从1到now的hash值
scanf("%s",s+1);
nowlen=strlen(s+1);
for(int now=1;now<=nowlen;++now)
	ha[now]=(ha[now-1]*base%mod+s[now])%mod;

就是这样了,这个模式大概不错.
至于有没有更简便的写法...本蒟蒻不知道...希望神犇指点.
例题链接题解链接
5.4 update:
hash的思想在许多题目中都是可以运用的,它将一个串压成了一个正整数,我们就可以将一些状态去压缩为一个整形存储(类似状压,但比状压能够表达的状态要更为灵活)
例题链接题解链接

posted @ 2024-04-28 19:53  SLS-wwppcc  阅读(21)  评论(0编辑  收藏  举报