题目描述
给定若干个长度小于等于1e6的字符串,询问每个字符串最多由多少个相同的子串重复连接而成。如:ababab,最多由3个ab连接而成。
输入
输入有多组,每行一个只有小写字母组成的字符串。以"."为结束符
输出
对于每组输入,输出一行,表示答案。
样例输入
abcd aaaa ababab .
样例输出
1 4 3
题解
这题之前有KMP的方法,哈希的着手点在于记录下给定字符串和所有前缀的哈希值,可以用一个数组来存储,ha[i]表示前i位(包括i)组成的字符串的hash值(这里的hash值第0位不能为1,也要算进去),再用一个数组保存p的i次方的值。然后我们枚举可能的字串长度(能够整除)i,不断的进行比较。
例如abcabc,当我们枚举到i=3的时候,当前字串尾下标是i-1(下标从0开始),字串是abc,子串的hash值h=ha[i-1],与原字符串第0到i-1串的hash值进行比较(可省略这一步),相等的话,比较的位置向后移动i位,与0+i到2*i-1串的hash值进行比较,不相等就直接break,否则重复上面的步骤向后移动。
在程序中我们是用j来表示比较位置的末尾(j=i-1,2*i-1,…),用第j-i到第j位之间的hash值和子串的hash值h进行比较。第j-i到第j位之间的hash值等于第0到第j位之间的hash值ha[j] - 第0位到第j-i位的hash值ha[j-i]*k[i]。
公式:ha[j]=ha[j-i]*(p^i)+ha[i-1]
=>ha[j]=ha[j-i]*k[i]+ha[i-1]
=>ha[i-1]=ha[j]-ha[j-i]*k[i]
如果字串的hash值h不等于ha[i-1],就break。
由于我们的循环是从子串长度由小到大循环的,也就意味着子串数量是由多到少,所以只要出现了满足的子串就可以直接输出并且break。
#include<iostream>
#include<cstring>
#include<string>
using namespace std;
typedef unsigned long long ull;
const int maxn=1e6+5;
char s[maxn];
ull p=31,k[maxn],ha[maxn];
int main()
{
k[0]=1;
for(int i=1;i<maxn;i++)
k[i]=k[i-1]*p;//判断数量级
while(cin>>s)
{
if(s[0]=='.')
break;
int len=strlen(s);
ha[-1]=0;
for(int i=0;i<len;i++)
ha[i]=ha[i-1]*p+s[i]-'A'+1;
for(int i=1;i<=len;i++)
{
if(len%i!=0)
continue;
ull h=ha[i-1];
int j;
for(j=i-1;j<len;j+=i)
{
if(h!=(ha[j]-ha[j-i]*k[i]))
break;
}
if(j>=len)
{
cout<<len/i<<endl;
break;
}
}
}
return 0;
}