第2部分 字符串算法(提高篇)--第1章 哈希和哈希表1457:Power Strings

1457:Power Strings

时间限制: 1000 ms 内存限制: 65536 KB
提交数: 1280 通过数: 515
【题目描述】
原题来自:POJ 2406

给定若干个长度 ≤106 的字符串,询问每个字符串最多是由多少个相同的子字符串重复连接而成的。如:ababab 则最多有 3 个 ab 连接而成。

【输入】
输入若干行,每行有一个字符串,字符串仅含英语字母。特别的,字符串可能为 . 即一个半角句号,此时输入结束。

【输出】
【输入样例】
abcd
aaaa
ababab
.
【输出样例】
1
4
3


思路:假设 b = 3,数的范围为0-63 (代码中实际情况是,b=131,mod = 1<<31,数据类型为unsigned long long)

对于字符串abcabc:

               index:  0      1      2           3           4         5

               arr:    a      b      c           a            b        c

               hash:  56      61     41          34          11        3    
               
                (计算公式:hash[i]=hash[i+1]*b+s1[i]-‘a‘+1)         这里面有个问题是:因为数的范围为0-63 ,最大为63,可能存在数的溢出,比如:

从左往右,a->c这一步,hash[2] = hash[3] * 3+‘c‘-‘a‘+1 =34 * 3+2+1= 105。105大于63,这么办?
在计算机中会自动取模(也就是取余数):105% 64=41。其他 hash值都是这么计算的,每步都会取余数的。
同样,power的计算也是如此:3 9 27 17(81%64) …
那么计算机怎么判断前三个字符(“abc”)与后三个字符(“abc”)相等呢?取模后计算会不会出现负值?
后三个字符:tmp0 = hash[3]-hash[6] * power[3] = 34 - 0 * 27 = 34
前三个字符:tmp1 = hash[0]-hash[3] * power[3] = 56 -34 * 27 = ? 。这里34* 27实际是(34* 27)%64=22,所以56-22=34。
所以tmp0=tmp1。(程序中用函数封装)

#include
#include
#include
#include
const int maxn = 1e6+10;
typedef unsigned long long ull;
using namespace std; 
ull power[maxn],h[maxn];
char s1[maxn];
ull s,n,b = 131,mod = 1<<31;//b = 131取该值HASH值产生冲突的概率极低
int check(ull v, int k){
	for(ull i = 0; i < n; i += k)//计算字符串哈希值是否相等
	{
		if(h[i+k]-h[i]*power[k] != v) return 0;//Hash 不等 返回0
	}
	return 1;//Hash 相等 返回1
}
int main(){
	power[0] = 1;
	for(int i = 1; i <= 101000;i++)
	power[i] = power[i-1]*b;
	while(scanf("%s",s1+1)){
		if(s1[1] == '.') break;//.退出
		n = strlen(s1+1);
		h[0] = 0;
		for(int i = 1; i <= n; i++)
		h[i] = h[i-1]*b + (ull)(s1[i]-'A'+1);//哈希值
		for(int i = 1; i <=n; i++)
		{
			if(n%i==0)//如果重复,长度必为第一次出现字母的前面这个小字符串长度的整数倍
			{
				if(check(h[i],i)==1){//找到相同串
					printf("%d\n",n/i);
					break;
				}
			}
		}
	}
	return 0;
}

你可能感兴趣的:(信息学C++,一本通,哈希算法)