Problem Description
Given two strings a and b we define a*b to be their concatenation. For example, if a = "abc" and b = "def" then a*b = "abcdef". If we think of concatenation as multiplication, exponentiation by a non-negative integer is defined in the normal way: a^0 = "" (the empty string) and a^(n+1) = a*(a^n).
Input
Each test case is a line of input representing s, a string of printable characters. The length of s will be at least 1 and will not exceed 1 million characters. A line containing a period follows the last test case.
Output
For each s you should print the largest n such that s = a^n for some string a.
Sample Input
abcd aaaa ababab .
Sample Output
1 4 3
题目大意:给一个字符串S长度不超过10^6,求最大的n使得S由n个相同的子串连接而成。如:"ababab"则由n=3个"ab"连接而成,"aaaa"由n=4个"a"连接而成,"abcd"则由n=1个"abcd"连接而成。
sol:本题实质是求最小循环节。注意:这里的循环节是要完全覆盖整个母串,而不只是母串的前缀。
方法1:用hash值求循环节。
基本思路是:枚举循环节的长度,从S串划分出若干当前长度的子串,判断划分出来的每一小段hash值是否都相等,若相等,则该长度为最小循环节长度。当然枚举的长度要为S串长度的约数。如s="abcabcabcabc",len=1,"a"<>“b",1不是最小循环节长度。len=2,"ab"<>"ca",所以2不是最小循环节长度。len=3,"abc"="abc"="abc"="abc",3为最小循环节长度。
代码实现:
1 #include<iostream>
2 #include<cstdio>
3 #include<string>
4 #include<cstring>
5 using namespace std;
6 typedef unsigned long long LL;
7 const LL base=131;
8 const int N=1000010;
9 int n;
10 LL power[N],sum[N];
11 bool check(LL v,int k) //k为枚举的长度,判断s[1]~s[k]是否是循环节
12 {
13 for(register int i=1;i<=n;i+=k) //从s串的第一个位置开始,以k为长度划分
14 {
15 if(v!=sum[i+k-1]-sum[i-1]*power[k])//一路推过去每k位都应该值等于v,才是循环节
16 return 0;
17 }
18 return 1;
19 }
20 int main()
21 {
22 power[0]=1;
23 for(register int i=1;i<=N-10;++i) //hash准备工作
24 power[i]=power[i-1]*base;
25 char s[N];
26 while(scanf("%s",s+1))
27 {
28 if(s[1]=='.')break;
29 n=strlen(s+1);
30 sum[0]=0;
31 for(register int i=1;i<=n;++i)
32 sum[i]=sum[i-1]*base+LL(s[i]);
33 for(register int i=1;i<=n;++i)
34 {
35 if(n%i) //循环节长度i,应为n的约数
36 continue;
37 LL expect=sum[i];//前i位的hash值
38 if(check(expect,i))//检测s串以每i位划分出来,每一段是否相等
39 {
40 printf("%d
",n/i);//检测成功,i为最小循环节长度,输出n/i的值
41 break;
42 }
43 }
44 }
45 return 0;
46 }
方法1的优化:枚举循环节的长度len,当然应是总长度的约数。用s的总长度-len求得一个数值num。判断S串的前num个字符的子串的hash值是否和后num个子串的hash值相等,若相等,len为最小循环节长度。如:s="abcabcabcabc",总长度为12,枚举长度len=3时,12-3=9,前9个字符和后9个字符相等,3为最小循环节长度。
证明:len=3,我们把该串分为4段,s1 s2 s3 s4,若前9位和后9位hash值相同,则有s1 s2 s3=s2 s3 s4,则有s1=s2,s2=s3,s3=s4,即4段相等。
方法2:KMP
根据next[i]的定义,字符串中以i为结束位置的后i位与第一位开始的前i位相等,根据上面hash优化方法里的证明,且本题为完全覆盖,我们只要求出next[len]的值(len为s总长度),若len%(len-next(len)=0,则len-next(len)为循环节。
s="abcabcabcabc",S的总长度len=12,求得next(12)的值为9,12%(12-9)=0,3为最小循环节长度。
1 #include<cctype>
2 #include<cstdio>
3 #include<cstring>
4 using namespace std;
5 int nxt[1000010],m;
6 char b[1000010];
7 void get_next()
8 {
9 memset(nxt,0,sizeof(nxt));
10 int j=0;
11 for(int i=2;i<=m;i++)
12 {
13 while(j&&b[i]!=b[j+1])j=nxt[j];
14 if(b[j+1]==b[i])nxt[i]=++j;
15 }
16 }
17 int main()
18 {
19 while(1)
20 {
21 scanf("%s",b+1);
22 if(b[1]=='.')break;
23 m=strlen(b+1);
24 get_next();
25 //此题求的东西与后面的poi那个正好相反
26 //例如"abcabcabc",易知next[9]=6,即字符串前缀长度为6的,正好等于后缀长度为6
27 //如果存在循环节,则9-next[9]这个数字应该为9的约数
28 //这样形如s1s2s3
29 //等于 s1s2s3
30 //即s2s3这一段等于s1s2这一样,于是我们将其等长划分后
31 //s2=s1,s3=s2,于是s1=s2=s3
32 if(m%(m-nxt[m])==0)
33 printf("%d
",m/(m-nxt[m]));
34 else
35 puts("1");
36 }
37 }