好玩的哈希

哈希

哈希这种东西还是挺好玩的。先说一说他最基本的作用:加入你想要 \(\mathcal O(1)\) 判断两个字符串或者其他东西是否相等,那么就用哈希吧。
哈希通俗的来讲,就是将一些超级大的数,映射到比较小的值域之中。但这势必会出现这样的问题:存在两个数,他们的哈希值相同,但本身不同,在判断的时候就死了。这就叫做 哈希冲突
注意,只有离散化这种哈希永远不会出现哈希冲突!!

用到哈希的主要有两种好处。

  1. 数字化。我们可以把一些复杂抽象的东西变为数字化的可以存储的东西。这样也就更好的比较。
  2. 避免出现 \(\log\)

具体的我们可以看几道题。

P3498 [POI 2010] KOR-Beads

首先一眼想到 \(\mathcal O(n\ln n)\)。这是个经典结论,可以给一个简单的证明。
\(\sum\limits_{i=1}^n\frac{n}{i}=n\int_0^n \frac{1}{n}=n\ln n\)
然后就做完了。关键在于如何截取一段字符串,如果一个字符一个字符的加入,那么就会获得 \(\mathcal O(n^2\log n\ln n)\)\(\log\)set 复杂度。
考虑用哈希。我们用哈希预处理这一段字符串,这样就可以去掉一个 \(n\)!!!!,复杂度 \(\mathcal O(n\log n\ln n)\)

P2757 [国家集训队] 等差子序列

这道题非常地好。我来讲讲我们是如何才能一步一步地想到正解的。
首先,看到题目中说 \(Len\ge 3\),那么我们就可以无耻的去掉 \(Len>3\) 的情况,只考虑 \(Len=3\) 的情况。毕竟如果这都不行的话,谈何大于 \(3\) 的呢?
接着,我们就有了一个很朴素的思路。假设值域为 \(3\),那么我们就考虑做特殊的:中间值。
我们枚举中间值的下标 \(i\),那么怎么判断是否合法呢?

  1. 枚举左边的值的位置。
    这个想法绝对朴素。我们以 \(a_i\) 为中间值,枚举 \(a_j,1\le j\le i-1\),然后去找是否存在 \(a_k,i+1\le k\le n\),使得 \(a_k+a_j=2_ai\)。这样复杂度是 \(\mathcal O(n^3)\)。考虑优化到极致,我们可以用一个桶去记录 \(i+1\)\(n\) 的所有值。然后这样我们就可以去掉一个 \(n\),这样我们的复杂度极致即为 \(\mathcal O(n^2)\)
    那么我们怎么才能摆脱平方呢?
  2. 关键:不去枚举左边的值的位置,直接在值域上思考。
    显然发现,枚举位置完全无法继续优化了。这个时候就要去挖掘题目性质:\(a_1,\cdots,a_n\)\(1\)\(n\) 的一种排列。
    那么怎么利用这一种性质呢?
    我们可以举个例子,比如你枚举的是 \(5\),那么有哪些是可以的呢?显然就是 \(1,9\)\(2,8\)\(3,7\)\(4,6\)
    那么怎么判断是否合法呢?由于我们中间值的下标是从小到大枚举的,所以我们可以利用这种性质!
    我们给之前枚举过的值打上标记 \(1\),然后我们发现,如果关于 \(5\) 对称的数,也就是 \(1,9\)\(2,8\)\(3,7\)\(4,6\),他们每一对的标记都相同,那么就表明他们,他们要么都没遍历过,也就是他们在 \(5\) 的同一侧!

然后就做完了。假设以 \(a_i\) 为中心的左右两端的 \(0,1\) 标记完全对等,那么就说明不合法,否则合法。这也就是判回文。
然后线段树判是否回文即可。期望复杂度为 \(\mathcal O(n\log n)\),显然可以通过。

bzoj#P3097. Hash Killer I

这道题是一道构造题,写完这道题可以锻炼人类智慧。
我们假设你有一个字符串 \(S_i\),表示级别 \(i\) 的字符串。那么我们定义 \(\overline{S_i}\)\(S_i\) 的反串。
假设 \(S_1\)\(ab\),那么 \(\overline{S_1}=ba\)
然后来进行神之一笔。我们另 \(S_i=S_{i-1}+\overline{S_{i-1}}\)
\(H1_i\) 计算 \(S_i\) 的哈希值,设 \(H2_i\) 计算 \(\overline{S_i}\)
那么我们就会发现,按照题目中的哈希计算方法,就会有这种情况。

\[\begin{aligned} & H1_i=H1_{i-1}base^{2^{i-1}}+H2_{i-1}\\\ & H2_i=H2_{i-1}base^{2^{i-1}}+H1_{i-1} \end{aligned} \]

然后这道题就快做完了。我们假设可以 \(H1_i=H2_j\),那么两个哈希值做减法,假设是 \(f_i\),那么:

\[f_i=f_{i-1}\times (base^{2^{i-1}}-1) \]

我们展开一下:

\[f_i=f_{i-1}(base^{2^{i-2}}+1)(base^{2^{i-3}}+1)\cdots (base+1)(base-1) \]

然后显然发现假设 \(base\) 为奇数,那么我们 \(f_i\) 就会有 \(2^{i(i-1)}\) 这个因子。
然后这道题就做完了。当我们取 \(i\ge 12\),即可通过本题!

6233. Hash killer V

这道题更唐。我们取 mo 的最小公倍数,此时他摸任何 \(mo_i\) 都为 \(0\),然后就构造完了。然后把这个哈希值转为26进制就行。

CF580E Kefa and Watch

这道题是这个样子的。看到第一个操作一眼想到线段树,主要的问题是第二个询问怎么处理。
最暴力的做法就是先去截取 \([l,l+x-1]\) 这一段字符串,然后一个一个拼接,最后看和线段树里的哈希值是否相同。
但是这样是 \(\mathcal O(n^2)\),考虑优化。
首先看到哈希想到哈希满足可加性,其次发现我们可以对他进行二进制拆分,即我们可以用倍增优化。
这样这道题就是 \(\mathcal O(n\log ^2n)\) 的。可以通过这道题。
解释:\(\mathcal O(n\log ^2n)\) 是因为线段树 pushdown 操作时也要倍增。
注意:CF 卡自然溢出!!!

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5,BASE=131,MOD=998244353;
string c; 
int n,m,k,f[20],P[N<<4];
struct SGT
{
	int sum[N<<2],tag[N<<2];
	void pushup(int x,int l,int r)
	{
		int mid=(l+r)>>1;
		sum[x]=(sum[x<<1]*P[r-mid]%MOD+sum[x<<1|1])%MOD;
	}
	void pushdown(int x,int l,int r)
	{
		if(tag[x]==-1) return;
		int mid=(l+r)>>1;
		tag[x<<1]=tag[x<<1|1]=tag[x];
		f[0]=tag[x];
		for(int i=1,j=1;i<=18;i++,j<<=1)
			f[i]=(f[i-1]*P[j]%MOD+f[i-1])%MOD;
		int len=mid-l+1,cnt=0;
		for(int i=18;~i;i--)
		{
			int p=1<<i;
			if(len<p) continue;
			cnt=(cnt+f[i]*P[len-p]%MOD)%MOD;
			len-=p;
		}
		sum[x<<1]=cnt;
		len=r-mid,cnt=0;
		for(int i=18;~i;i--)
		{
			int p=1<<i;
			if(len<p) continue;
			cnt=(cnt+f[i]*P[len-p]%MOD)%MOD;
			len-=p;
		}
		sum[x<<1|1]=cnt;
		tag[x]=-1;
	}
	void build(int x,int l,int r)
	{
		tag[x]=-1; 
		if(l==r)
		{
			sum[x]=c[l]-'0';
			return; 
		}
		int mid=(l+r)>>1;
		build(x<<1,l,mid),build(x<<1|1,mid+1,r);
		pushup(x,l,r);
	}
	void upd(int x,int l,int r,int ql,int qr,int c)
	{
		if(ql<=l&&r<=qr)
		{
			int len=r-l+1,cnt=0;
			f[0]=c,tag[x]=c;
			for(int i=1,j=1;i<=18;i++,j<<=1)
				f[i]=(f[i-1]*P[j]%MOD+f[i-1])%MOD;
			for(int i=18;~i;i--)
			{
				int p=1<<i;
				if(len<p) continue;
				cnt=(cnt+f[i]*P[len-p]%MOD)%MOD;
				len-=p;
			}
			sum[x]=cnt;
			return ; 
		}
		int mid=(l+r)>>1;pushdown(x,l,r);
		if(ql<=mid) upd(x<<1,l,mid,ql,qr,c);
		if(qr>mid) upd(x<<1|1,mid+1,r,ql,qr,c);
		pushup(x,l,r);
	}
	int query(int x,int l,int r,int ql,int qr)
	{
		if(ql>qr) return 0;
		if(ql<=l&&r<=qr) return sum[x];
		pushdown(x,l,r);int mid=(l+r)>>1;
		if(ql>mid) return query(x<<1|1,mid+1,r,ql,qr);
		else if(qr<=mid) return query(x<<1,l,mid,ql,qr);
		return (query(x<<1,l,mid,ql,mid)*P[qr-mid]%MOD+query(x<<1|1,mid+1,r,mid+1,qr))%MOD;
	}
}T;
signed main()
{
	cin.tie(0)->sync_with_stdio(0);
	cout.tie(0)->sync_with_stdio(0);
	cin>>n>>m>>k>>c;c=" "+c;
	P[0]=1;
	for(int i=1;i<=n*2;i++) P[i]=P[i-1]*BASE%MOD;
	T.build(1,1,n);
	int q=m+k;
	for(;q;q--)
	{
		int op,l,r,x;
		cin>>op>>l>>r>>x;
		if(op==1) T.upd(1,1,n,l,r,x);
		else
		{
			int len=r-l+1;
			if(len<x)
			{
				cout<<"YES\n";
				continue;
			}
			int cnt=T.query(1,1,n,l,l+(len%x)-1);
			f[0]=T.query(1,1,n,l,l+x-1);
			for(int i=1,j=1;i<=__lg(len/x);i++,j<<=1)
				f[i]=(f[i-1]*P[j*x]%MOD+f[i-1])%MOD;
			int sum=0;len=(len/x)*x;
			for(int i=__lg(len/x);~i;i--)
			{
				int p=(1<<i)*x;
				if(len<p) continue;
				sum=(sum+f[i]*P[len-p]%MOD)%MOD;
				len-=p;
			}
			len=r-l+1;sum=(sum*P[len%x]%MOD+cnt)%MOD;
			if(sum==T.query(1,1,n,l,r))
				cout<<"YES\n";
			else
				cout<<"NO\n";
		}
	}
	return 0;
}

ABC398F

这种题简单说说就行了。首先一个关键的观察就是我们希望他的字符串尽量短,也就是这个 \(S\) 和它的反串 \(S'\) 尽量有公共前缀。
也就是说我们希望找到 \(S\)最大后缀回文串
这道题做完了。注意,ABC 也不能自然溢出!!!*。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e6+5,BASE=13331,MOD=998244353;
int hs1[N],hs2[N],n,P[N];
string s;
int cal(const int &x,const int &y)
{
	if(x<y) return x-y+MOD;
	return x-y;
}
signed main()
{
	cin>>s;n=s.size();
	P[0]=1;
	for(int i=1;i<=n;i++)
		P[i]=P[i-1]*BASE%MOD;
	for(int i=1;i<=n;i++)
		hs1[i]=(hs1[i-1]*BASE%MOD+(s[i-1]-'A'))%MOD;
	for(int i=n;i;i--)
		hs2[i]=(hs2[i+1]*BASE%MOD+(s[i-1]-'A'))%MOD;
	int id;
	for(int i=1;i<=n;i++)
		if(cal(hs1[n],hs1[i-1]*P[n-i+1]%MOD)==hs2[i])
		{
			id=i;
			break;
		}
	if(id!=1)
	{
		string ss=s.substr(0,id-1);
		reverse(ss.begin(),ss.end());
		s+=ss;
	}
	cout<<s; 
	return 0;
}
posted @ 2025-03-16 14:57  I_AK_CTSC  阅读(27)  评论(0)    收藏  举报