洛谷 P3370 字符串哈希 (模板)
<题目链接>
<转载于 >>> >
题目描述
如题,给定N个字符串(第i个字符串长度为Mi,字符串内包含数字、大小写字母,大小写敏感),请求出N个字符串中共有多少个不同的字符串。
输入格式:
第一行包含一个整数N,为字符串的个数。
接下来N行每行包含一个字符串,为所提供的字符串。
输出格式:
输出包含一行,包含一个整数,为不同的字符串个数。
说明
时空限制: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
最稳妥的办法是选择两个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\n",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\n",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\n",ans); 36 }
2018-10-31
作者:is_ok
出处:http://www.cnblogs.com/00isok/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。