哈希冲突及解决办法

0x00 前言

在上一节中,我们提到了哈希冲突这一在做题中极为常见的错误
点击传送门,即可阅读
在做题中,我们在处理字符串哈希的时候,有可能会遇到两个字符串(这里以字符串距离)并不相同,但是计算后的两个哈希值相同,这时候我们就需要处理哈希冲突了

0x01 正文

在面对哈希冲突时,我们除了上文中的两个方法
还可以使用 双哈希

顾名思义,将一个字符串算两遍哈希

在上一文章中我们已经写出了简单版的单哈希
h a s h ( s ) = Σ i = 0 n s [ i ] ∗ p n − i   % m o d hash(s)=\Sigma_{i=0}^{n}s[i]*p^{n-i}\ \%mod hash(s)=Σi=0ns[i]pni %mod
计算第二个哈希的时候,只需换一个底数质数就可以实现
我们直接整合了一个结构体里,方便使用;

struct HASH{
	long long sed,mod,h[N],pw[N];
	void init(int ser_in,int mod_in){
		sed=ser_in,mod=mod_in;
		pw[0]=1;
		for(int i=1;i<N;i++){
			pw[i]=pw[i-1]*sed%mod;
		}
	} 
	void make(string s){
		h[0]=s[0]%mod;
		for(int i=1;i<s.size();i++){
			h[i]=(h[i-1]*sed%mod+s[i])%mod;
		}
	}
	long long get(int l,int r){
		return (h[r]-h[l-1]*pw[r-l+1]%mod+mod)%mod;
	}
}s1,s2;

使用时:

struct HASH{
	long long sed,mod,h[N],pw[N];
	void init(int ser_in,int mod_in){
		sed=ser_in,mod=mod_in;
		pw[0]=1;
		for(int i=1;i<N;i++){
			pw[i]=pw[i-1]*sed%mod;
		}
	} 
	void make(string s){
		h[0]=s[0]%mod;
		for(int i=1;i<s.size();i++){
			h[i]=(h[i-1]*sed%mod+s[i])%mod;
		}
	}
	long long get(int l,int r){
		return (h[r]-h[l-1]*pw[r-l+1]%mod+mod)%mod;
	}
}s1,s2;

s1.init(131,1000000007);
s2.init(137,998244353);//使用不同的底数和大质数进行哈希 

string s;
cin>>s;

s=" "+s;

s1.make(s);
s2.make(s);//构造 

s1.get(l,r);
s2.get(l,r);//区间查询 

0x02 例题

例题
这道题中,他放我们找区间内的循环节,
由此我们可以知道,这个循环节一定是区间长度的因数
但是我们不知道应该除以几,(总不能一个一个的去试吧
这时我们可以利用欧拉筛,这个东西有一个特性,他会用每个合数的最小质因子标记他
哎,我们给他反过来不就行了
利用这一特性,再存一边每个数的最小质因子,需要的时候直接访问
这样,我们使用一个while循环,每次除以当前长度的最小值因子,再利用字符串哈希去比较,这不就行啦

tips:写的时候一定要注意小细节,不然很有可能debug半天

AC代码

#include<bits/stdc++.h>
using namespace std;

#define int long long
const int Ns=1e9+7;
const int N=1000005;

int prime[500020],g[500020];
bool vis[500020];

void ola(){
	int cnt=0; 
	vis[1]=g[1]=1;
	for(long long i=2;i<=500020;i++){
		if(!vis[i]){
			prime[++cnt]=i;
			g[i]=i;
		}
		for(int j=1;j<=cnt&&i*prime[j]<=500020;j++){
			vis[i*prime[j]]=1;
			g[i*prime[j]]=prime[j];
			if(i%prime[j]==0){
				break;
			} 
		}
	}
}


struct HASH{
	long long sed,mod,h[N],pw[N];
	void init(int ser_in,int mod_in){
		sed=ser_in,mod=mod_in;
		pw[0]=1;
		for(int i=1;i<N;i++){
			pw[i]=pw[i-1]*sed%mod;
		}
	} 
	void make(string s){
		h[0]=s[0]%mod;
		for(int i=1;i<s.size();i++){
			h[i]=(h[i-1]*sed%mod+s[i])%mod;
		}
	}
	long long get(int l,int r){
		return (h[r]-h[l-1]*pw[r-l+1]%mod+mod)%mod;
	}
}s1,s2;
#define endl "\n"
signed main(){
    
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    
	int n,q;string s;
	cin>>n>>s>>q;
	
	s=' '+s;
	s1.init(131,998244353);
	s1.make(s);
	ola();
	
	for(int Q=1;Q<=q;Q++){
		int l,r;
		cin>>l>>r;
		
		int len=r-l+1;
		int ans=len;
		while(len>1){
			int ans1=s1.get(l,r-ans/g[len]);
			int ans2=s1.get(l+ans/g[len],r);
			if(ans1==ans2){
				ans/=g[len];
			}
			len/=g[len];
		}
		cout<<ans<<endl;
	}
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值