概述
Hash算法模板
//hash一般用来解决字符串判重/字符串匹配问题
//遇见不定长问题可通过二分+hash降低复杂度
//遇见定长字符串问题可通过尺取+hash来降低复杂度
//二维hash的时候尺取方法就是把之前不需要的都变为0再加上当前行,将匹配字符串整体下移,来验证hash值是否相等
//---------------------------------单Hash-----------------------
#include<string.h>
typedef unsigned long long ull;
const int maxn=1e5+5;
ull hash_[maxn],xp[maxn];
void init()
{
xp[0]=1;
for(int i=1;i<maxn;i++)
xp[i]=xp[i-1]*13331;//这里13331玄学数字,大概可以随意换
return ;
}
void make_hash(char str[])//处理出str的hash值
{
int len=strlen(str);
hash_[len]=0;
for(int i=len-1;i>=0;i--)
{
hash_[i]=hash_[i+1]*13331+str[i]-'A'+1;
}
return ;
}
ull Get_hash(int i,int L)//得到起点为i,长度为L的子串的hash值
{
return hash_[i]-hash_[i+L]*xp[L];
}
//---------------------------------双Hash-----------------------
ll hash_[2][maxn],xp[2][maxn];
const int Mod1 = 1000000007;
const int Mod2 = 19260817;
void init()
{
xp[0][0]=1;
for(int i=1;i<maxn;i++)
xp[0][i]=xp[0][i-1]*13331%Mod1;
xp[1][0]=1;
for(int i=1;i<maxn;i++)
xp[1][i]=xp[1][i-1]*1331%Mod2;
return ;
}
void make_hash(char str[])
{
int len=strlen(str);
hash_[0][len]=0;
hash_[1][len]=0;
for(int i=len-1;i>=0;i--)
{
hash_[0][i]=(hash_[0][i+1]*13331%Mod1+str[i]-'A'+1)%Mod1;
hash_[1][i]=(hash_[1][i+1]*1331%Mod2+str[i]-'A'+1)%Mod2;
}
return ;
}
pll Get_hash(int i,int L)//得到起点为i,长度为L的子串的hash值
{
return pll((hash_[0][i]-hash_[0][i+L]*xp[0][L]%Mod1+Mod1)%Mod1,(hash_[1][i]-hash_[1][i+L]*xp[1][L]%Mod2+Mod2)%Mod2);
}
//---------------------------------多Hash-----------------------
//T是Hash次数
//hash_interval [a,b)
template <int T>
struct StringHash
{
std::vector<std::vector<int>> ha, pw;
std::vector<int> M;
explicit StringHash(const std::string& s)
: StringHash(std::vector<int>(s.begin(), s.end())) {}
explicit StringHash(const std::vector<int>& vec)
{
pw = ha =
std::vector<std::vector<int>>(T, std::vector<int>(vec.size() + 1));
std::vector<int> C;
for (int i = 0; i < T; i++)
{
pw[i][0] = 1;
C.push_back(rand_int());
M.push_back(rand_int());
}
for (int z = 0; z < T; z++)
{
for (size_t i = 0; i < vec.size(); ++i)
{
ha[z][i + 1] = (1LL * ha[z][i] * C[z] + vec[i]) % M[z],
pw[z][i + 1] = 1LL * pw[z][i] * C[z] % M[z];
}
}
}
// hash value of interval [a, b)
std::vector<int> hash_interval(int a, int b)
{
std::vector<int> ret(T);
for (int z = 0; z < T; z++)
{
ret[z] = (ha[z][b] - 1LL * ha[z][a] * pw[z][b - a] % M[z] + M[z]) % M[z];
}
return ret;
}
static int rand_int()
{
static std::mt19937 gen((std::random_device())());
static std::uniform_int_distribution<int> uid(1e8, 1e9);
return uid(gen);
}
};
Hash第一题
HDU1686
题意就是找A串在B串中的出现次数(可重叠)。其实就是一个kmp模板题,但是我们可以用hash很容易的解决
我们得到A串的hash值,然后在B中枚举起点,长度为lena的子串,检验hash值是否相同就可以了。
HDU1686代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
typedef unsigned long long ull;
const int maxn = 1e6+5;
ull xp[maxn],hash_1[maxn],hash_2[maxn];
void init()
{
xp[0]=1;
for(int i=1;i<maxn;i++)
xp[i]=xp[i-1]*13331;
}
ull get_hash(int i,int L,ull hash_[])//get_hash(i,L)可以得到从位置i开始的,长度为L的子串的hash值.
{
return hash_[i]-hash_[i+L]*xp[L];
}
int make_hash(char str[],ull hash_[])
{
int len=strlen(str);
hash_[len]=0;
for(int i=len-1;i>=0;i--)
{
hash_[i]=hash_[i+1]*13331+(str[i]-'a'+1);
}
return len;
}
char str[maxn],str2[maxn];
int main()
{
init();
int t;
scanf("%d",&t);
while(t--)
{
int ans=0;
scanf("%s%s",str,str2);
int len1=make_hash(str,hash_1);
int len2=make_hash(str2,hash_2);
ull tmp=get_hash(0,len1,hash_1);
for(int i=0;i<len2-len1+1;i++)//注意枚举时的边界问题
{
if(get_hash(i,len1,hash_2)==tmp)
ans++;
}
printf("%dn",ans);
}
return 0;
}
Hash第二题
POJ2774
题意就是求两个串的最长公共子串,正常做法应该是后缀数组跑一下height数组,这里说一下hash的做法。
由于没有给定长度,要求长度,这时就要想是否具有二分的性质,发现答案是具有二分性质的,所以我们可以二分答案,然后把A串中所有出现过的hash值放进一个数组,sort一下,然后对于每个B串产生的hash用lower_bound查询是否出现过,若出现过则直接返回true.复杂度是
o
(
l
e
n
∗
l
o
g
(
l
e
n
)
∗
l
o
g
(
l
e
n
)
)
o(len*log(len)*log(len))
o(len∗log(len)∗log(len)),跑了1250ms,用map判断是否出现过就会超时,这暂且作为hash的一个优化方式吧。
POJ2774代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<map>
using namespace std;
typedef unsigned long long ull;
const int maxn = 1e6+5;
ull xp[maxn],hash_1[maxn],hash_2[maxn];
ull a[maxn];
int len1,len2;
void init()
{
xp[0]=1;
for(int i=1;i<maxn;i++)
xp[i]=xp[i-1]*13331;
}
ull get_hash(int i,int L,ull hash_[])
{
return hash_[i]-hash_[i+L]*xp[L];
}
int make_hash(char str[],ull hash_[])
{
int len=strlen(str);
hash_[len]=0;
for(int i=len-1;i>=0;i--)
{
hash_[i]=hash_[i+1]*13331+(str[i]-'a'+1);
}
return len;
}
char str[maxn],str2[maxn];
bool check(int L)
{
int cnt=0;
for(int i=0;i<len1-L+1;i++)
{
a[cnt++]=get_hash(i,L,hash_1);
}
sort(a,a+cnt);
for(int i=0;i<len2-L+1;i++)
{
ull tmp=get_hash(i,L,hash_2);
int pos=lower_bound(a,a+cnt,tmp)-a;
if(a[pos]==tmp) return true;
}
return false;
}
int main()
{
init();
while( scanf("%s%s",str,str2)!=EOF)
{
len1=make_hash(str,hash_1);
len2=make_hash(str2,hash_2);
int l=0,r=min(len1,len2),mid;
while(l<=r)
{
mid=(l+r)>>1;
if(check(mid)) l=mid+1;
else r=mid-1;
}
printf("%dn",r);
}
return 0;
}
Hash第三题
POJ3261
本题就是求字符串中至少出现过k次的最长子串。
正解应该是后缀数组,二分长度之后在height上分块去做即可
Hash的做法也很明显,二分长度,之后去计算当前长度出现次数是否有超过k的子串就可以了
POJ3261代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<map>
using namespace std;
typedef unsigned long long ull;
const int maxn = 1e5+5;
ull xp[maxn],hash_[maxn];
ull a[maxn];
int len1,len2;
map<ull ,int> mp;
void init()
{
xp[0]=1;
for(int i=1;i<maxn;i++)
xp[i]=xp[i-1]*13331;
}
ull get_hash(int i,int L)
{
return hash_[i]-hash_[i+L]*xp[L];
}
void make_hash(ull str[],int len)
{
hash_[len]=0;
for(int i=len-1;i>=0;i--)
{
hash_[i]=hash_[i+1]*13331+str[i];
}
}
ull str[maxn];
bool check(int L,int len,int k)
{
mp.clear();
for(int i=0;i<len-L+1;i++)
{
ull tmp=get_hash(i,L);
mp[tmp]++;
if(mp[tmp]>=k) return true;
}
return false;
}
int n,k;
int main()
{
init();
while( scanf("%d%d",&n,&k)!=EOF)
{
for(int i=0;i<n;i++) scanf("%llu",&str[i]);
make_hash(str,n);
int l=0,r=n,mid;
while(l<=r)
{
mid=(l+r)>>1;
if(check(mid,n,k)) l=mid+1;
else r=mid-1;
}
printf("%dn",r);
}
return 0;
}
Hash第四题
UVA-257
本题意义是给你一堆字符串,将其中具有两个长度超过3的回文串的字符串输出
要求这两个回文字符串不能完全覆盖,但是可以重合,而且这两个回文字符串不能相同。
由于要求长度大于3而且可以重合,我们只要求每个长度为3/4的回文串就可以了,当某个位置存在长度>=3的回文串,如果串长为奇数,那么就取3,否则取4,然后把遍历指针i往后挪一位(去掉覆盖的情况),之后用hash判重就可以了。
比如AAAA
我们找到第一个AAA 然后把i++,这样就避免了AAAA覆盖AAA的情况
因为我们只考虑长度为3/4的回文串,所以只考虑这一种覆盖情况即可。
求某个位置为中心回文串的长度时,可以用manacher算法来计算出每个位置的回文半径。
UVA-257代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<sstream>
#include<map>
using namespace std;
typedef unsigned long long ull;
const int maxn=110010;
char s[maxn];
map<ull,int> mp;
char Ma[maxn*2];
int Mp[maxn*2];
string line;
string str;
ull hash_[maxn],xp[maxn];
void init()
{
xp[0]=1;
for(int i=1;i<maxn;i++)
xp[i]=xp[i-1]*13331;
return ;
}
void make_hash(char str[])
{
int len=strlen(str);
hash_[len]=0;
for(int i=len-1;i>=0;i--)
{
hash_[i]=hash_[i+1]*13331+str[i]-'A'+1;
}
return ;
}
ull Get_hash(int i,int L)
{
return hash_[i]-hash_[i+L]*xp[L];
}
void Manacher(char s[],int len)
{
int l=0;
Ma[l++]='$';
Ma[l++]='#';
for(int i=0; i<len; i++)
{
Ma[l++]=s[i];
Ma[l++]='#';
}
Ma[l]=0;
int mx=0,id=0;
for(int i=0; i<l; i++)
{
Mp[i]=mx>i?min(Mp[2*id-i],mx-i):1;
while(Ma[i+Mp[i]]==Ma[i-Mp[i]])
Mp[i]++;
if(i+Mp[i]>mx)
{
mx=i+Mp[i];
id=i;
}
}
}
int main()
{
int ans=0;
init();
while(scanf("%s",s)!=EOF)
{
mp.clear();
int len=strlen(s);
make_hash(s);
Manacher(s,len);//manacher之后,Mp[i]-1为i位置的回文半径
int cnt=0;
for(int i=0;i<2*len+2;i++)
{
if(Mp[i]-1>=3)
{
if(Mp[i]%2==1)//回文串为偶数,取长度四的回文串
{
int st=(i-1)/2-2;
int le=4;
ull tmp=Get_hash(st,le);
mp[tmp]++;
}
else//回文串为奇数,取长度三的回文串
{
int st=i/2-2;
int le=3;
ull tmp=Get_hash(st,le);
mp[tmp]++;
}
i++;//当前位置存在大于三的回文串,避免覆盖后移一位。
}
}
if(mp.size()>=2) printf("%sn",s);
}
return 0;
}
Hash第五题
UVA-11019
本体题意就是给你AB两个字符矩阵,问你B矩阵在A矩阵中的出现次数。
我们可以进行二维hash,其实就是把n个横向串连在一起hash。
注意判相等的时候,我们不断进行尺取+hash,尺取的过程,我们删除当前第一行的hash值加上最后一行的hash值,删除第一行的hash值直接删去就可以
例如
A
A
A
AAA
AAA
B
B
B
BBB
BBB
C
C
C
CCC
CCC
我们删去第一行的hash值 相当于把矩阵变成了
000
000
000
B
B
B
BBB
BBB
C
C
C
CCC
CCC
此时我们再添加最后一行
000
000
000
B
B
B
BBB
BBB
C
C
C
CCC
CCC
D
D
D
DDD
DDD
如果这时候的B矩阵是
B
B
B
BBB
BBB
C
C
C
CCC
CCC
D
D
D
DDD
DDD
这两个矩阵的hash值不同的,为了处理这种情况,我们把B矩阵相应的添加前几行
变成
000
000
000
B
B
B
BBB
BBB
C
C
C
CCC
CCC
D
D
D
DDD
DDD
这样再去匹配就可以了。
以上就是二维hash大概的处理方法(是我自己想的做法,如果有其他好的尺取方法欢迎指教
掌握了这个做法,我们就可以枚举矩阵的左上角,然后对于当前列数的矩阵从上向下进行尺取,hash判断就可以了。
UVA-11019代码
#include<stdio.h>
#include<algorithm>
#include<iostream>
using namespace std;
const int maxn = 1e3+5;
const int MAXN = 1e6+5;
typedef unsigned long long ull;
ull hash_[maxn][maxn],xp[MAXN];
char str[maxn][maxn];
char str2[maxn][maxn];
void init()
{
xp[0]=1;
for(int i=1;i<MAXN;i++)
{
xp[i]=xp[i-1]*13331;
}
return ;
}
ull Get_hash(int i,int j,int l)
{
return hash_[i][j]-hash_[i][j+l]*xp[l];
}
int main()
{
init();
int t;
scanf("%d",&t);
while(t--)
{
int ans=0;
int n,m,x,y;
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)
{
scanf("%s",str[i]);
}
for(int i=0;i<n;i++)
{
hash_[i][m]=0;
for(int j=m-1;j>=0;j--)
{
hash_[i][j]=hash_[i][j+1]*13331+str[i][j]-'a'+1;//每一行分别处理hash值
}
}
scanf("%d%d",&x,&y);
for(int i=0;i<x;i++)
scanf("%s",str2[i]);
ull tmp=0;
for(int i=x-1;i>=0;i--)
{
for(int j=y-1;j>=0;j--)
{
tmp=tmp*13331+str2[i][j]-'a'+1;//处理出匹配矩阵的hash值
}
}
for(int i=0;i<=m-y;i++)//枚举横坐标起点
{
ull tt=tmp;
ull tmp2=0;
for(int j=x-1;j>=0;j--)
{
tmp2=tmp2*xp[y]+Get_hash(j,i,y);
}
if(tt==tmp2) ans++;
for(int j=x;j<n;j++)
{
tmp2-=Get_hash(j-x,i,y)*xp[(j-x)*y];//尺取过程去除第一行,也就是将第一行变为0
tmp2+=Get_hash(j,i,y)*xp[j*y];//加上最后一行
tt=tt*xp[y];//将匹配矩阵第一行添上0
if(tmp2==tt) ans++;
}
}
printf("%dn",ans);
}
return 0;
}
Hash第六题
HDU4821
本题题意就是给你一个字符串,问你该字符串具有多少个子串满足
长度为ml,而且可以拆成m个长度为l的不相同子串。
定长字符串问题,很明显是可以hash+尺取的,我们可以枚举l个子串的起点,之后算出当前ml的字符串的情况,之后不断往后尺取就可以了
复杂度是
o
(
l
∗
(
l
e
n
/
l
)
∗
l
o
g
(
m
)
)
o(l*(len/l)*log(m))
o(l∗(len/l)∗log(m)) 因为尺取的过程每次向后跳l个字符,log是map带来的所以复杂度是可以的
然而如果枚举所有起点的m*l字符串验证复杂度就会变成
o
(
l
e
n
∗
l
)
o(len*l)
o(len∗l) 是会超时的
这就是利用hash+尺取带来的优势。
HDU4821代码
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<map>
using namespace std;
typedef unsigned long long ull;
#define dbg(x) cout<<#x<<" = "<<x<<endl;
#define Get_hash(i,L) hash_[i]-hash_[i+L]*xp[L]
const int maxn = 1e5+5;
ull xp[maxn],hash_[maxn];
ull x[maxn];
char str[maxn];
void init()
{
xp[0]=1;
for(int i=1;i<maxn;i++)
xp[i]=xp[i-1]*13331;
return ;
}
int Make_hash(char str[])
{
int len=strlen(str);
hash_[len]=0;
for(int i=len-1;i>=0;i--)
hash_[i]=hash_[i+1]*13331+(str[i]-'a'+1);
return len;
}
map<ull,int> mp;
int main()
{
init();
int m,l;
while(scanf("%d%d",&m,&l)!=EOF)
{
int cnt=0;
scanf("%s",str);
int len=Make_hash(str);
for(int i=0;i<len-l+1;i++)
{
x[i]=Get_hash(i,l);
}
for(int i=0;i<l&&i+m*l<=len;i++)//枚举起点,注意范围
{
mp.clear();
int ans=0;
for(int j=i;j<i+m*l;j+=l)
{
if(mp[x[j]]==0)
{
ans++;
}
mp[x[j]]++;
}
if(ans==m) cnt++;//以上是用来计算出第一个完整的m*l块的情况,之后尺取就可以了
for(int j=i+m*l;j+l<=len;j+=l)//注意范围
{
ull tmp=x[j-m*l];
mp[tmp]--;//去掉当前第一个长度为l的子串
if(mp[tmp]==0) ans--;
tmp=x[j];//加上当前长度l的子串
if(mp[tmp]==0)
{
ans++;
}
mp[tmp]++;
if(ans==m) cnt++;//当前的m个均不相同
}
}
printf("%dn",cnt);
}
return 0;
}
Hash第七题
HDU2859
求最大对称子矩阵,对称是延对角线对称,由于本题给出的对角线不方便操作,我们将所有字符串逆置一下,就变成了好操作的对角线,然后我们对每一行每一列进行hash,dp的时候只要从左上角dp值一直减小到0,判断是否有len满足向上和向左的hash值相等。
HDU2859代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
typedef unsigned long long ull;
const int maxn = 1e3+5;
ull hash_1[maxn][maxn],hash_2[maxn][maxn],xp[maxn];
char str[maxn][maxn];
void init()
{
xp[0]=1;
for(int i=1;i<maxn;i++)
xp[i]=xp[i-1]*13331;//这里13331玄学数字,大概可以随意换
return ;
}
void make_hash(int n)//处理出str的hash值
{
for(int i=0;i<n;i++)
{
hash_1[i][n]=0;
for(int j=n-1;j>=0;j--)
{
hash_1[i][j]=hash_1[i][j+1]*13331+(str[i][j]-'A')+1;
}
}
for(int i=0;i<n;i++)
{
hash_2[i][n]=0;
for(int j=n-1;j>=0;j--)
{
hash_2[i][j]=hash_2[i][j+1]*13331+str[j][i]-'A'+1;
}
}
return ;
}
ull Get_hash1(int x,int i,int L)//得到起点为i,长度为L的子串的hash值
{
return hash_1[x][i]-hash_1[x][i+L]*xp[L];
}
ull Get_hash2(int x,int i,int L)//得到起点为i,长度为L的子串的hash值
{
return hash_2[x][i]-hash_2[x][i+L]*xp[L];
}
int dp[maxn][maxn];
int main()
{
int n;
init();
while(scanf("%d",&n)!=EOF)
{
if(n==0) break;
for(int i=0;i<n;i++) scanf("%s",str[i]);
for(int i=0;i<n;i++) strrev(str[i]);
make_hash(n);
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
dp[i][j]=1;
int ans=1;
for(int i=1;i<n;i++)
{
for(int j=1;j<n;j++)
{
int tmp=dp[i-1][j-1];
for(int len=tmp;len>=0;len--)
{
ull tmp1=Get_hash1(i,j-len,len+1);
ull tmp2=Get_hash2(j,i-len,len+1);
if(tmp1==tmp2)
{
dp[i][j]=len+1;
break;
}
}
ans=max(ans,dp[i][j]);
}
}
printf("%dn",ans);
}
return 0;
}
Hash第八题
CodeforcesMail.Ru Cup 2018 Round 3 E
题意
给你一个01串s,一个字符串t,
0
可
以
映
射
成
r
0
,
1
可
以
映
射
成
r
1
0可以映射成r_0,1可以映射成r_1
0可以映射成r0,1可以映射成r1
问
有
多
少
组
r
0
,
r
1
可
以
满
足
映
射
之
后
s
=
t
问有多少组r_0,r_1可以满足映射之后s=t
问有多少组r0,r1可以满足映射之后s=t
∣
s
∣
<
=
1
0
5
,
∣
t
∣
<
=
1
0
6
|s|<=10^5,|t|<=10^6
∣s∣<=105,∣t∣<=106
做法
我
们
设
置
s
串
中
0
的
个
数
为
n
u
m
0
,
s
串
中
1
的
个
数
为
n
u
m
1
我们设置s串中0的个数为num_0,s串中1的个数为num_1
我们设置s串中0的个数为num0,s串中1的个数为num1
如
果
固
定
r
0
的
长
度
,
也
就
固
定
了
r
1
的
长
度
,
也
就
确
定
了
r
0
和
r
1
如果固定r_0的长度,也就固定了r_1的长度,也就确定了r_0和r_1
如果固定r0的长度,也就固定了r1的长度,也就确定了r0和r1
由
于
n
u
m
0
∗
l
e
n
(
r
0
)
+
n
u
m
1
∗
l
e
n
(
r
1
)
=
l
e
n
(
t
)
由于num_0*len(r_0)+num_1*len(r_1)=len(t)
由于num0∗len(r0)+num1∗len(r1)=len(t)
我
们
只
要
枚
举
l
e
n
(
r
0
)
就
可
以
我们只要枚举len(r_0)就可以
我们只要枚举len(r0)就可以
首
先
要
判
断
l
e
n
1
是
否
为
整
数
,
之
后
枚
举
s
串
用
h
a
s
h
判
断
每
一
位
0
或
1
是
否
能
正
确
转
义
首先要判断len_1是否为整数,之后枚举s串用hash判断每一位0或1是否能正确转义
首先要判断len1是否为整数,之后枚举s串用hash判断每一位0或1是否能正确转义
如
果
每
一
位
都
可
以
转
义
那
么
a
n
s
+
+
,
注
意
这
里
h
a
s
h
用
自
动
溢
出
会
被
卡
,
需
要
多
h
a
s
h
如果每一位都可以转义那么ans++,注意这里hash用自动溢出会被卡,需要多hash
如果每一位都可以转义那么ans++,注意这里hash用自动溢出会被卡,需要多hash
复
杂
度
分
析
(
来
自
p
l
s
)
复杂度分析(来自pls)
复杂度分析(来自pls)
枚
举
0
的
长
度
l
e
n
0
,
那
么
就
有
n
/
l
e
n
0
个
可
行
解
枚举0的长度len0,那么就有n/len0个可行解
枚举0的长度len0,那么就有n/len0个可行解
每
个
可
行
解
要
做
n
次
h
a
s
h
每个可行解要做n次hash
每个可行解要做n次hash
对
于
l
e
n
0
>
n
/
2
,
总
复
杂
度
就
是
O
(
n
2
/
l
e
n
0
)
≈
O
(
n
)
对于len0>n/2,总复杂度就是O(n^2/len0) approx O(n)
对于len0>n/2,总复杂度就是O(n2/len0)≈O(n)
代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
#define dbg2(x1,x2) cout<<#x1<<" = "<<x1<<" "<<#x2<<" = "<<x2<<endl
typedef unsigned long long ull;
const int maxn = 1e6+10;
char str[maxn],t[maxn];
template <int T>
struct StringHash
{
std::vector<std::vector<int>> ha, pw;
std::vector<int> M;
explicit StringHash(const std::string& s)
: StringHash(std::vector<int>(s.begin(), s.end())) {}
explicit StringHash(const std::vector<int>& vec)
{
pw = ha =
std::vector<std::vector<int>>(T, std::vector<int>(vec.size() + 1));
std::vector<int> C;
for (int i = 0; i < T; i++)
{
pw[i][0] = 1;
C.push_back(rand_int());
M.push_back(rand_int());
}
for (int z = 0; z < T; z++)
{
for (size_t i = 0; i < vec.size(); ++i)
{
ha[z][i + 1] = (1LL * ha[z][i] * C[z] + vec[i]) % M[z],
pw[z][i + 1] = 1LL * pw[z][i] * C[z] % M[z];
}
}
}
// hash value of interval [a, b)
std::vector<int> hash_interval(int a, int b)
{
std::vector<int> ret(T);
for (int z = 0; z < T; z++)
{
ret[z] = (ha[z][b] - 1LL * ha[z][a] * pw[z][b - a] % M[z] + M[z]) % M[z];
}
return ret;
}
static int rand_int() {
static std::mt19937 gen((std::random_device())());
static std::uniform_int_distribution<int> uid(1e8, 1e9);
return uid(gen);
}
};
int num[2];
int main()
{
scanf("%s%s",str,t);
StringHash<10> sh(t);
int lens=strlen(str);
int lent=strlen(t);
int ans=0;
for(int i=0;i<lens;i++)
{
if(str[i]=='0') num[0]++;
else num[1]++;
}
for(int i=1;i*num[1]<lent;i++)
{
if(((lent-i*num[1])%num[0])!=0) continue;
int len0=(lent-i*num[1])/num[0];
int len1=i;
vector<int> flag0,flag1;
int f0=-1,f1=-1;
int flag=0;
int tmp=0;
for(int j=0;j<lens;j++)
{
if(str[j]=='0')
{
if(f0==-1)
{
f0=1;
flag0=sh.hash_interval(tmp,tmp+len0);
tmp+=len0;
}
else
{
vector<int> tt=sh.hash_interval(tmp,tmp+len0);
if(tt!=flag0)
{
flag=1;
break;
}
else
{
tmp+=len0;
}
}
}
else
{
if(f1==-1)
{
f1=1;
flag1=sh.hash_interval(tmp,tmp+len1);
tmp+=len1;
}
else
{
vector<int> tt=sh.hash_interval(tmp,tmp+len1);
if(tt!=flag1)
{
flag=1;
break;
}
else
{
tmp+=len1;
}
}
}
}
if(flag0!=flag1&&flag==0) ans++;
}
printf("%dn",ans);
return 0;
}
Hash第九题
题目链接
http://codeforces.com/contest/727/problem/E
题意
给你一个长度为
n
×
k
n times k
n×k的环,环上每一个位置有一个字符。
现在给你
g
g
g个长度为
k
k
k的字符串,问是否可以在
g
g
g个字符串中找出
k
k
k个构成这个环。
做法
由于单个字符串长度已经确定,只需要枚举第一个串在环中的下标即可。
而且只需要枚举到
k
k
k,因为枚举
1
1
1和
k
+
1
k+1
k+1是一样的。
对于每个枚举的起始位置,只需要每
k
k
k个字符判断一下是不是可以在
g
g
g个字符串中得到,这个过程用Hash直接可以做。
枚举起点
k
k
k次,每次计算
n
n
n个位置,总复杂度是
O
(
n
∗
k
)
O(n*k)
O(n∗k)
这题会卡掉自然溢出,所以要用多Hash,自己第一次手写这个东西,常数可能有点大。
代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<map>
#include<stack>
using namespace std;
typedef long long ll;
typedef pair <ll, ll> pll;
const int maxn = 5e6+5;
const int Mod=1000000007;
#define Se second
#define Fi first
#define pb push_back
char str[maxn];
char tmp[maxn];
ll hash_[2][maxn],xp[2][maxn];
const int Mod1 = 1000000007;
const int Mod2 = 19260817;
void init()
{
xp[0][0]=1;
for(int i=1;i<maxn;i++)
xp[0][i]=xp[0][i-1]*13331%Mod1;
xp[1][0]=1;
for(int i=1;i<maxn;i++)
xp[1][i]=xp[1][i-1]*1331%Mod2;
return ;
}
pll make_hash(char str[])
{
int len=strlen(str);
hash_[0][len]=0;
hash_[1][len]=0;
for(int i=len-1;i>=0;i--)
{
hash_[0][i]=(hash_[0][i+1]*13331%Mod1+str[i]-'A'+1)%Mod1;
hash_[1][i]=(hash_[1][i+1]*1331%Mod2+str[i]-'A'+1)%Mod2;
}
return pll((hash_[0][0]-hash_[0][len]*xp[0][len]%Mod1+Mod1)%Mod1,(hash_[1][0]-hash_[1][len]*xp[1][len]%Mod2+Mod2)%Mod2);
}
pll Get_hash(int i,int L)//得到起点为i,长度为L的子串的hash值
{
return pll((hash_[0][i]-hash_[0][i+L]*xp[0][L]%Mod1+Mod1)%Mod1,(hash_[1][i]-hash_[1][i+L]*xp[1][L]%Mod2+Mod2)%Mod2);
}
pll H[maxn];
map<pll,int> mp;
map<pll,stack<int> > mp2;
vector<pll> ct;
int main()
{
init();
int n,k,m;
scanf("%d%d",&n,&k);
scanf("%s",str);
for(int i=0;i<n*k;i++) str[i+n*k]=str[i];
str[2*n*k]='