匹配 hash
Tags: 字符串 hash
匹配 hash
https://www.cnblogs.com/henry-1202/p/10324966.html
LOJ 字串查找
一道入门题。
给定一个字符串
字符串哈希模板题。
思路
https://www.luogu.com.cn/article/2cuvkgn0
基于哈希的思想,我们可以将一个字符串通过哈希函数转换成为
可是,我们该如何获取字符串在区间
在字符串
然而,实际上我们会将字符串转换为
注意,我这里使用的是单哈希,如果
为什么会有哈希冲突?
我们的答案会呈现出
但是,我们会发现,有可能有多个数对
当然,你也可以不使用
#include <bits/stdc++.h>
//#define int long long
#define rep(i, l, r) for(int i = l; i <= r; ++ i)
#define per(i, r, l) for(int i = r; i >= l; -- i)
using namespace std;
const int N=1e6+10,base=233331;
char s1[N],s2[N];
int n1,n2;
unsigned long long f1[N],f2[N],G[N];
void init(){
G[0]=1;
rep(i,1,n1)G[i]=G[i-1]*base;
rep(i,1,n1)f1[i]=f1[i-1]*base+s1[i];
rep(i,1,n2)f2[i]=f2[i-1]*base+s2[i];
}
unsigned long long calc(int l,int r){
return f1[r]-f1[l-1]*G[r-l+1];
}
main(){
cin>>(s1+1)>>(s2+1);
n1=strlen(s1+1);n2=strlen(s2+1);
init();
int tot=0;
rep(i,1,n1-n2+1)if(calc(i,i+n2-1)==f2[n2])++tot;
cout<<tot;
return 0;
}
「一本通 2.1 练习 1」Power Strings
询问每个字符串最多是由多少个相同的子字符串重复连接而成的。
我们枚举字符串长度的约数,然后直接暴力哈希匹配判断即可。
这是后面一个题的弱化版,待会会讲解加强版。
#include <bits/stdc++.h>
//#define int long long
#define rep(i, l, r) for(int i = l; i <= r; ++ i)
#define per(i, r, l) for(int i = r; i >= l; -- i)
using namespace std;
const int N=2e6+10,base=233331;
unsigned long long f[N],G[N];
char s[N];
int n;
void init(){
G[0]=1;
rep(i,1,N-10)G[i]=G[i-1]*base;
}
unsigned long long calc(int l,int r){return f[r]-f[l-1]*G[r-l+1];}
void solve()
{
n=strlen(s+1);
rep(i,1,n)f[i]=f[i-1]*base+s[i];
for(int i=1;i<=n;++i){
if(n%i==0){
unsigned long long x=calc(1,i);
for(int j=1;j<=n;j+=i)if(calc(j,j+i-1)!=x)goto aa;
cout<<n/i<<'\n'; return;
aa:;
}
}
cout<<"1\n";return;
}
main(){
init();
for(;;)
{
cin>>(s+1);
if(s[1]=='.')exit(0);
solve();
}
return 0;
}
「一本通 2.1 练习 2」Seek the Name, Seek the Fame
这个题是下个题的必要条件,所以还是讲一下吧。
题意:对于每个字符串中求出所有既是前缀又是后缀的子串长度。
我们对于每一个前缀求出它的哈希值
现在给定
我们将问题转化:
现在有一个字符串
假设我们现在询问的区间为
接着再来思考这道题,由于
接下来,将得到的字符串分成左右两部分,并分别记录这两段的哈希值。然后枚举删中的字符,若所得字符串与后半段相等就统计答案,最后分类讨论即可。
#include <bits/stdc++.h>
#define int unsigned long long
#define rep(i, l, r) for(int i = l; i <= r; ++ i)
#define per(i, r, l) for(int i = r; i >= l; -- i)
using namespace std;
const int N=2e6+10;
int n,mid,ans_id,tot;
string s;
const int base=133331,p=1e9+7;
int G[N],f[N];
unordered_map<int,bool>vis;
void init()
{
G[0]=1;
rep(i,1,n){
G[i]=G[i-1]*base;
f[i]=f[i-1]*base+s[i];
}
}
int calc(int l,int r){return f[r]-f[l-1]*G[r-l+1];}
int del_calc(int l,int x,int r){
return calc(l,x-1)*G[r-x]+calc(x+1,r);
}
bool check(int x)
{
int l=0,r=0;
if(x==mid)
{
l=calc(1,x-1);
r=calc(x+1,n);
}
if(x<mid)
{
l=del_calc(1,x,mid);
r=calc(mid+1,n);
}
if(x>mid)
{
l=calc(1,mid-1);
r=del_calc(mid,x,n);
}
if(l==r&&!vis[l]){vis[l]=1;return 1;}
return 0;
}
main()
{
cin>>n>>s;
s="0"+s;
if(!(n&1))puts("NOT POSSIBLE"),exit(0);
mid=(n+1)>>1;
init();
rep(i,1,n)
if(check(i))
{
ans_id=i;
tot++;
}
if(tot>1)puts("NOT UNIQUE"),exit(0);
if(!tot)puts("NOT POSSIBLE"),exit(0);
if(ans_id==mid)rep(i,1,mid-1)cout<<s[i];
else if(ans_id<mid) rep(i,mid+1,n)cout<<s[i];
else rep(i,1,mid-1)cout<<s[i];
return 0;
}
「POI2010」珍珠项链 Beads
给出
首先,因为串可以被反转,所以我们要保存这个序列的倒序哈希。
代码长这样:
void init(){
g[0]=1;
rep(i,1,n)g[i]=g[i-1]*base;
per(i,n,1)f[i]=f[i+1]*base+a[i];
}
int calc(int l,int r){return f[l]-f[r+1]*g[r-l+1];}
接下来思考时间复杂度,枚举
这个可以看似近似
我们按照题面模拟就可以了,对于每一次判断,我们用 map 保存哈希值,然后判重即可,但是会发现会 TLE 一个点,考虑优化,对于每个
#include <bits/stdc++.h>
//#define int long long
#define rep(i, l, r) for(int i = l; i <= r; ++ i)
#define per(i, r, l) for(int i = r; i >= l; -- i)
using namespace std;
const int N=2e5+10,base=233331;
int n;
int a[N];
unsigned long long f1[N],f2[N],g[N];
int calc1(int l,int r){return f1[r]-f1[l-1]*g[r-l+1];}
int calc2(int l,int r){return f2[l]-f2[r+1]*g[r-l+1];}
void init(){
g[0]=1;
rep(i,1,n)g[i]=g[i-1]*base;
rep(i,1,n)f1[i]=f1[i-1]*base+a[i];
per(i,n,1)f2[i]=f2[i+1]*base+a[i];
}
int ans[N],cnt,tot;
unordered_map<int,bool>mp;
main()
{
cin>>n;
rep(i,1,n)cin>>a[i];
init();
rep(i,1,n){
if(n/i<tot)break;
int sum=0;
for(int j=1;j<=n;j+=i){
if(j+i-1>n)break;
int xx=calc1(j,j+i-1);
int yy=calc2(j,j+i-1);
if(!mp[xx]&&!mp[yy]){
mp[xx]=mp[yy]=1;
++sum;
}
}
if(sum>tot){
tot=sum;
cnt=1;
ans[1]=i;
}
else if(sum==tot)ans[++cnt]=i;
mp.clear();
}
cout<<tot<<' '<<cnt<<'\n';
rep(i,1,cnt)cout<<ans[i]<<' ';
return 0;
}
「POI2010」反对称 Antisymmetry
Manacher 模板题,不知道 pyf 会讲不。。。
这里提供一种哈希解法。
我们先求出异或后的倒序哈希,我们枚举每个回文串的中间轴,对于每一个
总结一下,Manacher 这种问题一般是求
考场上也有这种题,比如单调队列优化 dp 可以使用线段树写,这个时候就要仔细思考代码时空复杂度,代码长度和代码简易难度之间的关系。
#include <bits/stdc++.h>
//#define int long long
#define rep(i, l, r) for(int i = l; i <= r; ++ i)
#define per(i, r, l) for(int i = r; i >= l; -- i)
using namespace std;
const int N=5e5+10,base=233331;
int n;
unsigned long long f1[N],f2[N],g[N];
char s1[N],s2[N];
int calc1(int l,int r){return f1[r]-f1[l-1]*g[r-l+1];}
int calc2(int l,int r){return f2[l]-f2[r+1]*g[r-l+1];}
void init(){
g[0]=1;
rep(i,1,n)g[i]=g[i-1]*base;
rep(i,1,n)f1[i]=f1[i-1]*base+s1[i];
per(i,n,1)f2[i]=f2[i+1]*base+s2[i];
}
long long sum;
bool check(int x,int y){
return calc1(x,y)==calc2(x,y);
}
int solve(int x){
int l=0,r=min(n-x,x),ans=0;
for(;l<=r;){
int mid=(l+r)>>1;
if(check(x-mid+1,x+mid))ans=mid,l=mid+1;
else r=mid-1;
}
return ans;
}
main()
{
cin>>n;
cin>>(s1+1);
rep(i,1,n)s2[i]='0'+(s1[i]!='1');
init();
rep(i,1,n)
sum+=solve(i);
cout<<sum;
return 0;
}
[POI 2012] OKR-A Horrible Poem
一道深刻利用了字符串性质的题。
询问每个字符串的某个子串最多是由多少个相同的子字符串重复连接而成的。
盗个图,有一个结论,如果
我令下张图片上一行的字符串为截取的
对于一个字符串,我们可以发现,如果
另外循环节的长度的循环次数都一定是总长的约数,我们可以先预处理出每一个数的约数,存到
我们可以这样:
vector<int>v[N];
void prime(){
rep(i,1,n)
for(int j=i;j<=n;j+=i)v[j].push_back(i);
}
这么做就可以拿下 LOJ 的满分(评测鸡这么快)了,但是 luogu 和 bzoj 是过不了的。
我们使用
有了
最终,我们得到了
使用筛法构造最小质因数数组,步骤如下:
- 初始化默认每个数都是质数。
- 遍历
2 到\sqrt{n} 的所有整数: 若i 仍然是质数,则枚举i 的倍数j ,并更新spf_j =i (前提是spf_j 仍未被更新)。 - 经过遍历,每个合数
j 都会记录其最小的质因数。
int spf[N];
void solve(int n) {
for (int i = 1; i <= n; i++) spf[i] = i;
for (int i = 2; i * i <= n; i++) {
if (spf[i] == i) {
for (int j = i * i; j <= n; j += i) {
if (spf[j] == j)
spf[j] = i;
}
}
}
}
已知
例如,
对于每一次查询生成的约数,我们生成的约数在
在代码实现方面,需要一些优化,例如求解哈希函数时使用 inline
,读入和输出(不要使用递归输出,使用栈函数)优化
总结,这是一道巧妙运用字符串性质和数学的题目,这启发我们不断优化代码,不断思考算法的本质。
#include <bits/stdc++.h>
//#define int long long
#define rep(i, l, r) for(int i = l; i <= r; ++ i)
#define per(i, r, l) for(int i = r; i >= l; -- i)
using namespace std;
#define gc getchar
inline void read(int &n){
char c=getchar();
for(;c<48||c>57;c=getchar());
for(n=0;c>47&&c<58;c=getchar())n=(n<<1)+(n<<3)+c-48;
}
inline void write(int x){
if(x==0){putchar(48);return;}
char s[11];int i=0;
for(;x;x/=10)s[i++]=x%10+48;
for(;i;)putchar(s[--i]);
}
const int N=2e6+10;
const unsigned int base=97;
unsigned int f[N],G[N];
char s[N];
int n,spf[N];
void init(){
G[0]=1;
rep(i,1,n)G[i]=G[i-1]*base;
rep(i,1,n)f[i]=f[i-1]*base+s[i];
rep(i,1,n)spf[i]=i;
for(int i=2;i*i<=n;++i)
if(spf[i]==i)
for(int j=i*i;j<=n;j+=i)
if(spf[j]==j)spf[j]=i;
}
inline unsigned int calc(int l,int r){return f[r]-f[l-1]*G[r-l+1];}
void dfs(vector<pair<int,int>> &s,vector<int> &V,int pos,int sum){
if(pos==(int)s.size()){
V.push_back(sum);
return;
}
rep(i,0,s[pos].second){
dfs(s,V,pos+1,sum);
sum*=s[pos].first;
}
}
vector<int>V[N];
vector<int> to_get(int x) {
if(!V[x].empty())return V[x];
vector<pair<int,int>> s;
int tmp=x;
for(;tmp>1;){
int p=spf[tmp],cnt=0;
for(;tmp%p==0;++cnt,tmp/=p);
s.push_back({p,cnt});
}
dfs(s,V[x],0,1);
sort(V[x].begin(),V[x].end());
return V[x];
}
void solve(){
int x,y;read(x);read(y);
vector<int> v=to_get(y-x+1);
for(int d:v) if(calc(x,y-d)==calc(x+d,y)){write(d),putchar('\n');return;}
}
int main(){
read(n);
rep(i,1,n)s[i]=getchar();
init();
int Q;read(Q);
for(;Q;--Q)solve();
return 0;
}
相信你一定也能 A 了这道模板题,加油!!!
替代难写的后缀排序。
我们可以转化问题:如何快速判断两个字符串大小?
首先,我们可以先用
把这个实现放在排序的 cmp
函数就可以了,时间复杂度为
注意事项:
- 二分的时候一定要确定好
[l,r] 区间的左右边界,这个是可以加速的。 - 使用稳定的
stable_sort
,这个内部采用归并排序,当有额外内存可用时,复杂度为O(n\log n) (luogu 竟然卡我),如果内存很少,采用常数较大原地排序算法。
#include <bits/stdc++.h>
#define rep(i, l, r) for(int i = l; i <= r; ++ i)
#define per(i, r, l) for(int i = r; i >= l; -- i)
using namespace std;
const int N=1e6+10,base=233331;
int n;
char str[N];
int a[N];
unsigned int f[N],g[N];
void init(){
g[0]=1;
rep(i,1,n){
f[i]=f[i-1]*base+str[i];
g[i]=g[i-1]*base;
a[i]=i;
}
}
unsigned int calc(int l,int r){return f[r]-f[l-1]*g[r-l+1];}
bool cmp(int l1,int l2){
int l=1,r=min(n-l1+1,n-l2+1);
for(;l<=r;){
int mid=(l+r)>>1;
if(calc(l1,l1+mid-1)==calc(l2,l2+mid-1))l=mid+1;
else r=mid-1;
}
return str[l1+l-1]<str[l2+l-1];
}
main()
{
cin>>(str+1);n=strlen(str+1);
init();
stable_sort(a+1,a+n+1,cmp);
rep(i,1,n)cout<<a[i]<<' ';
return 0;
}