洛谷 P3370 字符串哈希 (模板)

<题目链接>

<转载于 >>>  >

题目描述

如题,给定N个字符串(第i个字符串长度为Mi,字符串内包含数字、大小写字母,大小写敏感),请求出N个字符串*有多少个不同的字符串。

输入格式:

第一行包含一个整数N,为字符串的个数。

接下来N行每行包含一个字符串,为所提供的字符串。

输出格式:

输出包含一行,包含一个整数,为不同的字符串个数。

输入样例#1: 复制
5
abc
aaaa
abc
abcc
12345
输出样例#1: 复制
4

说明

时空限制:1000ms,128M

数据规模:

对于30%的数据:N<=10,Mi≈6,Mmax<=15;

对于70%的数据:N<=1000,Mi≈100,Mmax<=150

对于100%的数据:N<=10000,Mi≈1000,Mmax<=1500

样例说明:

样例中第一个字符串(abc)和第三个字符串(abc)是一样的,所以所提供字符串的集合为{aaaa,abc,abcc,12345},故共计4个不同的字符串。

Tip: 感兴趣的话,你们可以先看一看以下三题:

BZOJ3097:http://www.lydsy.com/JudgeOnline/problem.php?id=3097

BZOJ3098:http://www.lydsy.com/JudgeOnline/problem.php?id=3098

BZOJ3099:http://www.lydsy.com/JudgeOnline/problem.php?id=3099

如果你仔细研究过了(或者至少仔细看过AC人数的话),我想你一定会明白字符串哈希的正确姿势的^_^

解题分析:

据我的理解,Hash就是一个像函数一样的东西,你放进去一个值,它给你输出来一个值。输出的值就是Hash值。一般Hash值会比原来的值更好储存(更小)或比较。

那字符串Hash就非常好理解了。就是把字符串转换成一个整数的函数。而且要尽量做到使字符串对应唯一的Hash值。

字符串Hash的种类还是有很多种的,不过在信息学竞赛中只会用到一种名为“BKDR Hash”的字符串Hash算法。

它的主要思路是选取恰当的进制,可以把字符串中的字符看成一个大数字中的每一位数字,不过比较字符串和比较大数字的复杂度并没有什么区别(高精数的比较也是O(n)O(n)的),但只要把它对一个数取模,然后认为取模后的结果相等原数就相等,那么就可以在一定的错误率的基础上O(1)O(1)进行判断了。

那么我们选择什么进制比较好?

首先不要把任意字符对应到数字0,比如假如把a对应到数字0,那么将不能只从Hash结果上区分ab和b(虽然可以额外判断字符串长度,但不把任意字符对应到数字0更加省事且没有任何副作用),一般而言,把a-z对应到数字1-26比较合适。

关于进制的选择实际上非常*,大于所有字符对应的数字的最大值,不要含有模数的质因子(那还模什么),比如一个字符集是a到z的题目,选择27、233、19260817 都是可以的。

模数的选择(尽量还是要选择质数):

绝大多数情况下,不要选择一个10^9109级别的数,因为这样随机数据都会有Hash冲突,根据生日悖论,随便找上sqrt{10^9}109个串就有大概率出现至少一对Hash 值相等的串(参见BZOJ 3098 Hash Killer II)。

最稳妥的办法是选择两个10^9109级别的质数,只有模这两个数都相等才判断相等,但常数略大,代码相对难写,目前暂时没有办法卡掉这种写法(除了卡时间让它超时)(参见BZOJ 3099 Hash Killer III)。

如果能背过或在考场上找出一个10^181018级别的质数(Miller-Rabin),也相对靠谱,主要用于前一种担心会超时,后一种担心被卡。

偷懒的写法就是直接使用unsigned long long,不手动进行取模,它溢出时会自动对2^64264进行取模,如果出题人比较良心,这种做法也不会被卡,但这个是完全可以卡的,卡的方法参见BZOJ 3097 Hash Killer I。

这是自然溢出hash(100):

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 using namespace std;
 5 
 6 typedef unsigned long long ull;
 7 ull base=131;
 8 ull a[10010];
 9 char s[10010];
10 int n;
11 inline void read(int &x){
12     x=0;int f=1;char c=getchar();
13     while(c<'0'||c>'9'){if(c=='-')f=-f;c=getchar();}
14     while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
15     x*=f;
16 }
17 ull Hash(char *str){
18     int len=strlen(str);
19     ull ans=0;
20     for(int i=0;i<len;i++)
21         ans=ans*base+(ull)s[i];
22     return ans;
23 }
24 int main(){
25     read(n);
26     for(int i=1;i<=n;i++){
27         scanf("%s",s);
28         a[i]=Hash(s);
29     }
30     sort(a+1,a+1+n);
31     int ans=1;
32     for(int i=2;i<=n;i++)
33         if(a[i]!=a[i-1])
34             ++ans;
35     printf("%d
",ans);
36 }

这是双hash(100):

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 using namespace std;
 5 typedef unsigned long long ull;
 6 ull base=131;
 7 struct Node{
 8     ull x,y;
 9 }a[10010];
10 char s[10010];
11 int n;
12 ull mod1=19260817;
13 ull mod2=19660813;
14 inline void read(int &x){
15     x=0;int f=1;char c=getchar();
16     while(c<'0'||c>'9'){if(c=='-')f=-f;c=getchar();}
17     while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
18     x*=f;
19 }
20 ull hash1(char *str){
21     int len=strlen(str);
22     ull ans=0;
23     for(int i=0;i<len;i++)
24         ans=(ans*base+(ull)s[i])%mod1;
25     return ans;
26 }
27 ull hash2(char *str){
28     int len=strlen(str);
29     ull ans=0;
30     for(int i=0;i<len;i++)
31         ans=(ans*base+(ull)s[i])%mod2;
32     return ans;
33 }
34 bool comp(Node a,Node b){return a.x<b.x;}
35 int main(){
36     read(n);
37     for(int i=1;i<=n;i++){
38         scanf("%s",s);
39         a[i].x=hash1(s);
40         a[i].y=hash2(s);
41     }
42     sort(a+1,a+1+n,comp);
43     int ans=1;
44     for(int i=2;i<=n;i++){
45         if(a[i].x!=a[i-1].x||a[i].y!=a[i-1].y)
46             ++ans;
47     }
48     printf("%d
",ans);
49 }

这是只用一个10^18质数的hash(100)

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 using namespace std;
 5 typedef unsigned long long ull;
 6 ull base=131;
 7 ull a[10010];
 8 char s[10010];
 9 int n;
10 ull mod=212370440130137957ull;
11 inline void read(int &x){
12     x=0;int f=1;char c=getchar();
13     while(c<'0'||c>'9')if(c=='-'){f=-f;c=getchar();}
14     while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
15     x*=f;
16 }
17 ull Hash(char *str){
18     int len=strlen(s);
19     ull ans=0;
20     for(int i=0;i<len;i++)
21         ans=(ans*base+(ull)s[i])%mod;
22     return ans;
23 }
24 int main(){
25     read(n);
26     for(int i=1;i<=n;i++){
27         scanf("%s",s);
28         a[i]=Hash(s);
29     }
30     sort(a+1,a+1+n);
31     int ans=1;
32     for(int i=2;i<=n;i++)
33         if(a[i]!=a[i-1])
34             ans++;
35     printf("%d
",ans);
36 }

2018-10-31