Hash表
Hash表又称散列表,一般由Hash函数(散列函数)与链表共同实现。
有一种“开散列”的解决方案是,建立一个邻接表结构,以Hash函数的值域作为表头数组head,映射后的值相同的原始信息被分到同一类,构成一个链表接在对应的表头之后,链表上的节点信息可以保存原始信息和一些统计数据。
Hash表主要包括两个基本操作:
1.计算Hash函数的值;
2.定位到对应的链表中依次遍历,比较。
定义Hash函数
H
(
a
i
,
1
,
a
i
,
2
,
.
.
.
,
a
i
,
3
)
=
(
∑
j
=
1
6
a
i
,
j
+
∏
j
=
1
6
a
i
,
j
)
m
o
d
P
H(a_{i,1},a_{i,2},...,a_{i,3})=(\sum_{j=1}^{6}a_{i,j}+\prod_{j=1}^{6}a_{i,j})mod P
H(ai,1,ai,2,...,ai,3)=(∑j=16ai,j+∏j=16ai,j)modP
显然,对于形状相同的雪花他们的Hash值相等。
建立哈希表,把N片雪花依次插入,对于每片雪花扫描其哈希值所对应的表头后面的链表,检查每一片没否和他相等。
#include<iostream>
#include<cstring>
using namespace std;
#define MAX_N 100000
int snow[MAX_N+5][6];
int head[MAX_N+5];
int nxt[MAX_N+5];
int p=9991;
int n;
int tot=0;
int H(int *a)
{
int sum=0,mul=1;
for(int i=0;i<6;i++)
{
sum=(sum+a[i])%p;
mul=1ll*mul*a[i]%p;
}
return (sum+mul)%p;
}
bool equal(int *a,int *b)
{
int flag;
for(int i=0;i<6;i++)
{
for(int j=3;j<6;j++)
{
flag=1;
for(int k=0;k<6;k++)
{
if(a[(i+k)%6]!=b[(j+k)%6])flag=0;
}
if(flag)return 1;
flag=1;
for(int k=0;k<6;k++)
{
if(a[(i+k)%6]!=b[(j-k+6)%6])flag=0;
}
if(flag)return 1;
}
}
return 0;
}
bool insert(int *a)
{
int val=H(a);
for(int i=head[val];i;i=nxt[i])
{
if(equal(snow[i],a))return 1;
}
memcpy(snow[++tot],a,6*sizeof(int));
nxt[tot]=head[val];
head[val]=tot;
return 0;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
int a[10];
for(int j=0;j<6;j++)
scanf("%d",&a[j]);
if(insert(a))
{
puts("Twin snowflakes found.");
return 0;
}
}
puts("No two snowflakes are alike.");
return 0;
}
字符串Hash
取一固定值P,把字符串看作P进制数,并分配一个大于0的数值,代表每种字符。 一般来讲a=1,b=2,z=26。取一固定值M,求出该P进制数对M的余数,作为该字符串的Hash值。
一般来讲P取131或者13331,Hash值产生的冲突极低,只要Hash值相同,我们就可以认为字符串是相等的。通常我们取 M = 2 64 M=2^{64} M=264,即直接使用unsigned long long 类型存储这个Hash值,在计算式产生移除相当于直接对M取模。
如果已知字符串T的Hash值为H(S),字符串S+T的Hash值为H(S+T),那么字符串T的Hash值
H
(
T
)
=
(
H
(
S
+
T
)
−
H
(
S
)
∗
P
l
e
n
g
t
h
(
T
)
H(T)=(H(S+T)-H(S)*P^{length(T)}
H(T)=(H(S+T)−H(S)∗Plength(T)
模板题
#include<iostream>
#include<cstring>
using namespace std;
typedef unsigned long long ull;
#define MAX_N 1000000
char s[MAX_N+5];
ull pwe[MAX_N+5]={1},sum[MAX_N+5];
int p=131;
int main()
{
scanf("%s",s+1);
int len=strlen(s+1);
for(int i=1;i<=len;i++)
{
pwe[i]=pwe[i-1]*p;
sum[i]=sum[i-1]*p+(s[i]-'a'+1);
}
int m;
cin>>m;
while(m--)
{
int l1,r1,l2,r2;
scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
ull t1=sum[r1]-sum[l1-1]*pwe[r1-l1+1];
ull t2=sum[r2]-sum[l2-1]*pwe[r2-l2+1];
if(t1==t2)printf("Yes\n");
else printf("No\n");
}
return 0;
}
枚举字符串每一个位置,二分向两边扩展的长度,对于枚举到的每一组左右两边的字符串比较它们的Hash值是否相等,注意要维护两个字符串前缀和数组,一个表示从左往右的前缀和,另一个表示从右往左,时间复杂度为O(nlogn),使用manacher算法可以把时间复杂度降到O(n)。
#include<iostream>
#include<cstring>
using namespace std;
typedef unsigned long long ull;
#define MAX_N 1000000
char s[MAX_N+5];
int p=131;
ull suml[MAX_N*2+5],sumr[MAX_N*2+5],pwe[MAX_N*2+5]={1};
int n;
int H(ull h[],int l,int r)
{
return h[r]-h[l-1]*pwe[r-l+1];
}
bool check(int p,int l)
{
if(H(suml,p-l,p-1)==H(sumr,n-(p+l)+1,n-(p+1)+1))return 1;
return 0;
}
int main()
{
int t=1;
while(scanf("%s",s+1),strcmp(s+1,"END"))
{
n=strlen(s+1)*2;
for(int i=n;i>0;i-=2)
{
s[i]=s[i/2];
s[i-1]='z'+1;
}
for(int i=1,j=n;i<=n;i++,j--)
{
pwe[i]=pwe[i-1]*p;
suml[i]=suml[i-1]*p+(s[i]-'a'+1);
sumr[i]=sumr[i-1]*p+(s[j]-'a'+1);
}
int ans=0;
for(int i=1;i<=n;i++)
{
int l=0,r=min(i-1,n-i),mid;
while(l<r)
{
mid=(l+r+1)>>1;
if(check(i,mid))l=mid;
else r=mid-1;
}
if(s[i-l]<='z')ans=max(ans,l+1);
else ans=max(ans,l);
}
cout<<"Case "<<t++<<": "<<ans<<endl;
}
return 0;
}
最外层是快速排序sort,关键是cmp的设计。如果对每一对字符串的字典序进行比较,时间复杂度为O(n),效率较低。可以预处理字符串Hash,之后可以做到二分查找每一对字符串前缀的位置,时间复杂度为O(logn),找到之后比较后面的一个位置便可以直接比较出两个字符串的字典序,总体时间复杂度为O(nlognlogn)。
对于第二部分求相邻两个位置的前缀长度,可以直接调用前面提到的求前缀长度的函数。
#include<iostream>
#include<algorithm>
#include<limits.h>
#include<cstring>
using namespace std;
typedef unsigned long long ull;
#define MAX_N 300000
char s[MAX_N+5];
ull sum[MAX_N+5],pwe[MAX_N+5]={1};
int ind[MAX_N+5];
int p=131,len;
ull get(int l,int r)
{
return sum[r]-sum[l-1]*pwe[r-l+1];
}
int get_qz(int a,int b)
{
int l=0,r=len-max(a,b)+1,mid;
while(l<r)
{
mid=(l+r+1)>>1;
ull q1=get(a,a+mid-1),q2=get(b,b+mid-1);
if(q1==q2)l=mid;
else r=mid-1;
}
return l;
}
bool cmp(int a,int b)
{
int t=get_qz(a,b);
int l=(a+t>len?INT_MIN:s[a+t]),r=(b+t>len?INT_MIN:s[b+t]);
return l<r;
}
int main()
{
scanf("%s",s+1);
len=strlen(s+1);
for(int i=1;i<=len;i++)
{
sum[i]=sum[i-1]*p+(s[i]-'a'+1);
pwe[i]=pwe[i-1]*p;
ind[i]=i;
}
sort(ind+1,ind+1+len,cmp);
for(int i=1;i<=len;i++)
printf("%d ",ind[i]-1);
cout<<endl<<"0 ";
for(int i=2;i<=len;i++)
printf("%d ",get_qz(ind[i-1],ind[i]));
return 0;
}