字符串Hash总结

**~~

字符串Hash总结

**~~
Hash的题好像没怎么写过,所以专门练一下数论看不下去了我还是学字符串吧
Hash简单介绍:
hash是一种暴力算法。而且要明白的是,对于两个相等的元素他们的哈希值一定相等,但哈希值相等并不一定意味着两个元素一定会相等,也就是说hash会存在冲突问题。为了解决这一类问题,我们一般采取多重hash来降低冲突。
具体上,将字符串中的每一个字母都看成一个数字,选取多组两个合适的质数 Base 简写为 b , mod 简写为 h。必须要保证b和h互质,且b要小于h,否则会大大增加冲突率。
设C是一字符串,C=c1c2c3c4…cn
定义哈希函数:H( C ) = [ c1b^(n-1) + c2b^(n-2) + … + c0*b^0] mod h
预处理出1-i每个前缀的hash值,对于取子串,
比如我们要取出 i ~ i+k 这一段字串C‘ 哈希值。H(C’)= [ H(i+k) - H(i) * b^k) ] mod h
可以自行模拟一下 3124
3 31 312 3121 取12即为312-3 * 10^2
不一定需要mod,可以采用unsigned long long 自然溢出代替mod(除了某些毒瘤题卡这个)
下面先放模板(注意根据实际情况替换字符)
相关宏定义 下面不单独放了

#include<bits/stdc++.h>
#define si(a) scanf("%d",&a)
#define sl(a) scanf("%lld",&a)
#define sd(a) scanf("%lf",&a)
#define ss(a) scanf("%s",a)
#define ms(a) memset(a,0,sizeof(a))
#define repi(i,a,b) for(register int i=a;i<=b;++i)
#define repd(i,a,b) for(register int i=a;i>=b;--i)
#define reps(s) for(int i=head[s];i;i=e[i].nxt)
#define ce(i,r) i==r?'\n':' '
#define pb push_back 
#define fi first
#define se second
#define pr(x) cout<<#x<<": "<<x<<endl
using namespace std;

单hash

/*
字符串输入ss(s+1)
base 131 1e9+7 998244353 19260817
877785899 599160479 835442017 713976749 696336107 603345527
*/
typedef unsigned long long ull;
const int MAX_N=;
const ull base=131;
char s[MAX_N];
ull p[MAX_N];
ull h[MAX_N];
void init()
{
	p[0]=1;
	repi(i,1,MAX_N-1) p[i]=p[i-1]*base;
}
void get_h(char s[],ull h[],int len)
{
	h[0]=0;
	repi(i,1,len) h[i]=h[i-1]*base+s[i]-'a'+1;
}
ull get_sub(ull h[],int l,int r)
{
	return h[r]-h[l-1]*p[r-l+1];
}

//取模版
typedef unsigned long long ull;
const int MAX_N=1e6+5;
const ull base=131;
const ull mod=1e9+7;
ull p[MAX_N];
ull h[MAX_N];
void init()
{
	p[0]=1;
	repi(i,1,MAX_N-1) p[i]=p[i-1]*base%mod;
}
void get_h(char s[],ull h[],int len)
{
	h[0]=0;
	repi(i,1,len) h[i]=h[i-1]*base%mod+s[i]-'a'+1,h[i]%=mod;
}
ull get_sub(ull h[],int l,int r)
{
	return (h[r]+mod-h[l-1]*p[r-l+1]%mod)%mod;
}

双重hash

typedef unsigned long long ull;
typedef pair<ull,ull> P;
const ull base1=131;
const ull base2=1e9+7;
const int MAX_N=;
char s[MAX_N];
P p[MAX_N];
P h[MAX_N];
void init()
{
	p[0].fi=p[0].se=1;
	repi(i,1,MAX_N-1) p[i].fi=p[i-1].fi*base1,p[i].se=p[i-1].se*base2;
}
void get_h(char s[],P h[],int len)
{
	h[0].fi=h[0].se=0;
	repi(i,1,len){
		h[i].fi=h[i-1].fi*base1+s[i]-'a'+1;	
		h[i].se=h[i-1].se*base2+s[i]-'a'+1;
	} 
}
P get_sub(P h[],int l,int r)
{
	return P(h[r].fi-h[l-1].fi*p[r-l+1].fi,h[r].se-h[l-1].se*p[r-l+1].se);
}
//单取模版
const ull base1=131;
const ull base2=998244353;
const ull mod=1e9+7;
const int MAX_N=2e6+5;
P p[MAX_N];
P h[MAX_N];
void init()
{
	p[0].fi=p[0].se=1;
	repi(i,1,MAX_N-1) p[i].fi=p[i-1].fi*base1%mod,p[i].se=p[i-1].se*base2;
}
void get_h(char s[],P h[],int len)
{
	h[0].fi=h[0].se=0;
	repi(i,1,len){
		h[i].fi=(h[i-1].fi*base1%mod+s[i]-'a'+1)%mod;	
		h[i].se=h[i-1].se*base2+s[i]-'a'+1;
	} 
}
P get_sub(P h[],int l,int r)
{
	return P((h[r].fi+mod-h[l-1].fi*p[r-l+1].fi%mod)%mod,h[r].se-h[l-1].se*p[r-l+1].se);
}

具体选择上,一般题目直接单hash自然溢出就行了,求稳并且时间空间允许的情况双hash。不过在CF上是有人专门造卡hash的数据,CF上就建议双重hash+单取模了。
好像还有个多重hash但我感觉用不到就不管了
有关题目:

1.hdu1686 模板题
2.hdu2859 与dp结合,直接dp也可以不过可以用hash加速转移,先将给的串全部反转便于操作,对每个点取其左上角的dp值做上界,逐步减小check就行了,具体见代码

typedef unsigned long long ull;
const int MAX_N=1e6+5;
const ull base=131;
ull p[MAX_N];
ull hr[1005][1005],hc[1005][1005];
char s[1005][1005]; 
int n,dp[1005][1005];
void init()
{
	p[0]=1;
	repi(i,1,MAX_N-1) p[i]=p[i-1]*base;
}
void get_h()
{
	repi(i,1,n){
		hr[i][0]=0;
		repi(j,1,n) hr[i][j]=hr[i][j-1]*base+s[i][j]-'a'+1;
	}
	repi(i,1,n){
		hc[i][0]=0;
		repi(j,1,n) hc[i][j]=hc[i][j-1]*base+s[j][i]-'a'+1;
	}
}
ull get_sub(ull h[],int l,int r)
{
	return h[r]-h[l-1]*p[r-l+1];
}
int main()
{
	init();
	while(~si(n)&&n)
	{
		repi(i,1,n) ss(s[i]+1),reverse(s[i]+1,s[i]+1+n);
		get_h();
		repi(i,1,n)repi(j,1,n) dp[i][j]=1;
		repi(i,2,n)repi(j,2,n)repd(k,dp[i-1][j-1],1){
			ull tl=get_sub(hr[i],j-k,j),tc=get_sub(hc[j],i-k,i);
			if(tl==tc){
				dp[i][j]=k+1;
				break;
			}
		}
		int ans=0;
		repi(i,1,n)repi(j,1,n) ans=max(ans,dp[i][j]);
		printf("%d\n",ans);
	}
	return 0;
}

3.Uva257 枚举串长为3 4的回文串即可,有关判不能覆盖,考虑实际能覆盖的情况只能是aaa与aaaa(字符可替换),每次取回文后i++

const int MAX_N=1e5+5;
const ull base=131;
char s[MAX_N];
ull p[MAX_N];
ull h[MAX_N];
int len;
void init()
{
	p[0]=1;
	repi(i,1,MAX_N-1) p[i]=p[i-1]*base;
}
void get_h(char s[],ull h[],int len)
{
	h[0]=0;
	repi(i,1,len) h[i]=h[i-1]*base+s[i]-'A'+1;
}
ull get_sub(ull h[],int l,int r)
{
	return h[r]-h[l-1]*p[r-l+1];
}
char t[MAX_N<<1];
int dia[MAX_N<<1],lp;
int Manacher(char *s)
{
	int pos=0,i=0;
	t[pos]='%',lp=strlen(s+1);
	while(i<lp)	t[++pos]='#',t[++pos]=s[++i];
	t[++pos]='#';	// Ô¤´¦Àí
	int rm=0,id=0,ans=0;//µ±Ç°××Ó´®µ½´ïµÄ×îÓÒ¶Ë ¶ÔÓ¦µÄÖÐÐÄλÖÃ	
	t[pos+1]='\0';
	lp=pos;
	repi(i,2,lp){
		dia[i]=i<rm?min(rm-i,dia[2*id-i]):1;
		while(t[i-dia[i]]==t[i+dia[i]])	dia[i]++;
		if(dia[i]+i-1>rm) id=i,rm=dia[i]+i-1;
	}
	repi(i,1,lp) --dia[i],ans=max(ans,dia[i]); 
	return ans;	
}
set<ull> S;
bool check()
{
	S.clear();
	repi(i,2,lp)if(dia[i]>=3){
		int st,ed;
		if(dia[i]&1) st=i/2-1,ed=i/2+1;
		else st=i/2-1,ed=i/2+2;
		ull tmp=get_sub(h,st,ed);
		S.insert(tmp);
		i++;
	}
	return S.size()>=2;
}
int main()
{
	init();
	while(~ss(s+1))
	{
		len=strlen(s+1);
		get_h(s,h,len);
		Manacher(s);
		if(check()) printf("%s\n",s+1);
	}
	return 0;
}

4.CodeForces-1056E 枚举0 1长度即可,自然溢出被卡,换取模

const int MAX_N=1e6+5;
const ull base=131;
const ull mod=1e9+7;
ull p[MAX_N];
ull h[MAX_N];
void init()
{
	p[0]=1;
	repi(i,1,MAX_N-1) p[i]=p[i-1]*base%mod;
}
void get_h(char s[],ull h[],int len)
{
	h[0]=0;
	repi(i,1,len) h[i]=h[i-1]*base%mod+s[i]-'a'+1,h[i]%=mod;
}
ull get_sub(ull h[],int l,int r)
{
	return (h[r]+mod-h[l-1]*p[r-l+1]%mod)%mod;
}
char s[MAX_N],t[MAX_N];
int ls,lt;
bool check(int l0,int l1)
{
	bool f0=false,f1=false;
	ull t0,t1;
	int pos=1;
	repi(i,1,ls){
		if(s[i]=='0'){
			ull tmp=get_sub(h,pos,pos+l0-1);
			if(!f0) f0=true,t0=tmp;
			else if(t0!=tmp) return false;
			pos+=l0;
		}
		else{
			ull tmp=get_sub(h,pos,pos+l1-1);
			if(!f1) f1=true,t1=tmp;
			else if(t1!=tmp) return false;
			pos+=l1;
		}
	}
	return t0!=t1;
}
int main()
{
	init();
	ss(s+1),ss(t+1);
	ls=strlen(s+1),lt=strlen(t+1);
	get_h(t,h,lt);
	int n1=0,n0=0;
	repi(i,1,ls) n1+=(s[i]=='1'),n0+=(s[i]=='0');
	int ans=0;
	repi(i,1,lt){
		if((lt-n0*i)<=0) break;
		if((lt-n0*i)%n1==0){
			ans+=check(i,(lt-n0*i)/n1);
		} 
	}
	printf("%d\n",ans);
	return 0;
}

5.枚举起点即可,注意1-k就行了不用往后,往后会重复,需要双hash+取模 这些造数据的真毒瘤 nb

typedef unsigned long long ull;
typedef pair<ull,ull> P;
const ull base1=131;
const ull base2=998244353;
const ull mod=1e9+7;
const int MAX_N=2e6+5;
P p[MAX_N];
P h[MAX_N];
void init()
{
	p[0].fi=p[0].se=1;
	repi(i,1,MAX_N-1) p[i].fi=p[i-1].fi*base1%mod,p[i].se=p[i-1].se*base2;
}
void get_h(char s[],P h[],int len)
{
	h[0].fi=h[0].se=0;
	repi(i,1,len){
		h[i].fi=(h[i-1].fi*base1%mod+s[i]-'a'+1)%mod;	
		h[i].se=h[i-1].se*base2+s[i]-'a'+1;
	} 
}
P get_sub(P h[],int l,int r)
{
	return P((h[r].fi+mod-h[l-1].fi*p[r-l+1].fi%mod)%mod,h[r].se-h[l-1].se*p[r-l+1].se);
}
P get_h2(char s[],int len)
{
	P res=P(0,0);
	repi(i,1,len) res.fi=(res.fi*base1%mod+s[i]-'a'+1)%mod,res.se=res.se*base2+s[i]-'a'+1;
	return res;
}
int n,k,g;
int len;
char s[MAX_N],t[MAX_N];
map<P,int> mp;
vector<ull> ans;
set<P> S;
bool check(int st)
{
	ans.clear(),S.clear();
	for(int i=st,j=1;j<=n;i+=k,j++){
		P tmp=get_sub(h,i,i+k-1);
		if(mp[tmp]>0&&!S.count(tmp)) ans.pb(mp[tmp]),S.insert(tmp);
		else return false;
	}
	return true;
}
int main()
{
	init();
	si(n),si(k),ss(s+1);
	repi(i,n*k+1,2*n*k) s[i]=s[i-n*k];
	s[2*n*k+1]='\0';
	get_h(s,h,len=strlen(s+1));
	si(g);
	repi(i,1,g) ss(t+1),mp[get_h2(t,strlen(t+1))]=i;
	repi(i,1,k)if(check(i)){
		puts("YES");
		for(auto x:ans) printf("%d ",x);
		puts("");
		return 0;
	}
	puts("NO");
	return 0;
}

一些技巧:
尺取 通常是对定长的字符串的check
1.hdu4821
枚举起点(同样1-l即可后续会重复),对每个起点,先预处理出m个串,后每次后移动l,删除头部的串,加上尾部的新串

const int MAX_N=1e5+5;
const ull base=131;
ull p[MAX_N];
ull h[MAX_N];
char s[MAX_N]; 
int len;
void init()
{
	p[0]=1;
	repi(i,1,MAX_N-1) p[i]=p[i-1]*base;
}
void get_h(char s[],ull h[],int len)
{
	h[0]=0;
	repi(i,1,len) h[i]=h[i-1]*base+s[i]-'a'+1;
}
ull get_sub(ull h[],int l,int r)
{
	return h[r]-h[l-1]*p[r-l+1];
}
ull x[MAX_N];
int l,m;
map<ull,int> mp;
int main()
{
	init();
	while(~scanf("%d%d",&m,&l))
	{
		ss(s+1);
		len=strlen(s+1);
		get_h(s,h,len);
		int ml=m*l,ans=0;
		for(int i=1;i+l-1<=len;i++) x[i]=get_sub(h,i,i+l-1);
		for(int i=1;i<=l&&i+ml-1<=len;i++){
			mp.clear();
			int cnt=0;
			for(int j=i;j<=i+ml-1;j+=l){
				if(!mp[x[j]]) cnt++;
				mp[x[j]]++;
			} 
			if(cnt==m) ans++;
			for(int j=i+ml;j+l-1<=len;j+=l){
				mp[x[j-ml]]--;
				if(!mp[x[j-ml]]) cnt--;
				if(!mp[x[j]]) cnt++;
				mp[x[j]]++;
				if(cnt==m) ans++;
			}
		}
		printf("%d\n",ans);
	}
	return 0;
}

2.Uva11019
对每一行单独hash处理,对于矩阵的hash值,一行一行拼在后面即可。首行枚举起点,往下转移时,删去最下面一行,填上最上面一行。同时对匹配的矩阵扩大相应阶数。
详情见代码

const int MAX_N=1e6+5;
const ull base=131;
ull p[MAX_N];
ull h[1005][1005],h2[105][105];
int len;
void init()
{
	p[0]=1;
	repi(i,1,MAX_N-1) p[i]=p[i-1]*base;
}
void get_h(char s[],ull h[],int len)
{
	h[0]=0;
	repi(i,1,len) h[i]=h[i-1]*base+s[i]-'a'+1;
}
ull get_sub(ull h[],int l,int r)
{
	return h[r]-h[l-1]*p[r-l+1];
}
int n,m,r,c;
char s[1005][1005],t[105][105];
int main()
{
	init();
	int T;
	si(T);
	while(T--)
	{
		si(n),si(m);
		repi(i,1,n) ss(s[i]+1),get_h(s[i],h[i],m);
		si(r),si(c);
		repi(i,1,r) ss(t[i]+1),get_h(t[i],h2[i],c);
		ull tmp=0;
		repd(i,r,1) tmp=tmp*p[c]+h2[i][c];
		int ans=0;
		repi(i,1,m-c+1){
			ull t=tmp,t2=0;
			repd(j,r,1) t2=t2*p[c]+get_sub(h[j],i,i+c-1);
			if(t2==t) ans++;
			repi(j,r+1,n){
				t2-=get_sub(h[j-r],i,i+c-1)*p[(j-r-1)*c];
				t2+=get_sub(h[j],i,i+c-1)*p[(j-1)*c];
				t*=p[c];
				if(t2==t) ans++;
			}
		}
		printf("%d\n",ans);
	}
	return 0;
}

二分:处理不定长字符串
1.poj2774
求两个串最长公共子串,二分长度对于每次check,取s中所有对应长度的串,sort一下。然后对t中每个对应长度的串看是否有相同子串(lower_bound查询)

const int MAX_N=1e5+5;
const int base=131;
char s[MAX_N],t[MAX_N];
ull p[MAX_N];
ull h[MAX_N],h2[MAX_N];
int ls,lt;
void init()
{
	p[0]=1;
	repi(i,1,MAX_N-1) p[i]=p[i-1]*base;
}
void get_h(char s[],ull h[],int len)
{
	h[0]=0;
	repi(i,1,len) h[i]=h[i-1]*base+s[i]-'a'+1;
}
ull get_sub(ull h[],int l,int r)
{
	return h[r]-h[l-1]*p[r-l+1];
}
ull a[MAX_N];
bool check(int x)
{
	int cnt=0;
	repi(i,1,ls-x+1) a[++cnt]=get_sub(h,i,i+x-1);
	sort(a+1,a+1+cnt);
	repi(i,1,lt-x+1){
		ull tmp=get_sub(h2,i,i+x-1);
		if(a[lower_bound(a+1,a+1+cnt,tmp)-a]==tmp) return true;
	}
	return false;
}
int main()
{
	init();
	ss(s+1),ss(t+1);
	ls=strlen(s+1),lt=strlen(t+1);
	get_h(s,h,ls),get_h(t,h2,lt);
	int l=0,r=min(ls,lt),mid,ans;
	while(l<=r){
		int mid=(l+r)>>1;
		if(check(mid)) l=mid+1,ans=mid;
		else r=mid-1;
	}
	printf("%d\n",ans);
	return 0;
}

2.同样二分,和上题处理类似,放进数组再sort可处理时避免用map,用时会少一些

const int MAX_N=2e4+5;
const int base=131;
int s[MAX_N];
ull p[MAX_N];
ull h[MAX_N];
int n,k;
void init()
{
	p[0]=1;
	repi(i,1,MAX_N-1) p[i]=p[i-1]*base;
}
void get_h(int s[],ull h[],int len)
{
	h[0]=0;
	repi(i,1,len) h[i]=h[i-1]*base+s[i];
}
ull get_sub(ull h[],int l,int r)
{
	return h[r]-h[l-1]*p[r-l+1];
}
ull a[MAX_N];
bool check(int x)
{
	int cnt=0;
	repi(i,1,n-x+1) a[++cnt]=get_sub(h,i,i+x-1);
	sort(a+1,a+1+cnt);
	int mx=0,now=0;
	repi(i,1,cnt){
		now++;
		if(a[i]!=a[i+1]||i==cnt)
			mx=max(mx,now),now=0;
	}
	return mx>=k;
}
int main()
{
	init();
	si(n),si(k);
	repi(i,1,n) si(s[i]);
	get_h(s,h,n);
	int l=0,r=n,mid,ans;
	while(l<=r){
		mid=(l+r)>>1;
		if(check(mid)) l=mid+1,ans=mid;
		else r=mid-1;
	}
	printf("%d\n",ans);
	return 0;
}

3.UCF Local Programming Contest 2018(Practice)-F
不定长,二分,对于每次check,枚举起点,1-l,加入1-l后所有后缀,每次移动起点,将覆盖掉的后缀从mp中删去即可(也用到了尺取的思想),注意这里写法mp[]>0尽量不要用判!mp[] 避免多减一次变-1导致出错,用>0来判就没事

const int MAX_N=1e5+5;
const ull base=131;
char s[MAX_N];
ull p[MAX_N];
ull h[MAX_N],h2[MAX_N];
int len;
void init()
{
	p[0]=1;
	repi(i,1,MAX_N-1) p[i]=p[i-1]*base;
}
void get_h(char s[],ull h[],int len)
{
	h[0]=0;
	repi(i,1,len) h[i]=h[i-1]*base+(s[i]=='+')+1;
	h2[len+1]=0;
	repd(i,len,1) h2[i]=h2[i+1]*base+(s[i]=='-')+1;
}
ull get_sub(int l,int r)
{
	return h[r]-h[l-1]*p[r-l+1];
}
ull get_sub2(int l,int r)
{
	return h2[l]-h2[r+1]*p[r-l+1];
}
map<ull,int> mp;
bool check(int k)
{
	mp.clear();
	for(int i=1+k;i+k-1<=len;i++){
	mp[get_sub2(i,i+k-1)]++;	
	} 
	for(int i=1;i+2*k-1<=len;i++){
		ull now=get_sub(i,i+k-1);
		if(i>1) mp[get_sub2(i+k-1,i+2*k-2)]--;
		if(mp[now]>0) return true;
	}
	return false;
}
int main()
{
	init();
	ss(s+1);
	len=strlen(s+1);
	get_h(s,h,len);
	int l=0,r=len/2,mid,ans;
	while(l<=r){
		mid=(l+r)>>1;
		if(check(mid)) l=mid+1,ans=mid;
		else r=mid-1;
	}
	printf("%d\n",ans);
} 

参考文章 https://blog.csdn.net/qq_38891827/article/details/80723483

发布了6 篇原创文章 · 获赞 1 · 访问量 100

猜你喜欢

转载自blog.csdn.net/qq_44684888/article/details/105312536