Power string

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",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\n",m/(m-nxt[m]));
34         else 
35             puts("1");
36     }
37 }

 

posted @ 2020-03-20 16:44  蘑菇JJ  阅读(353)  评论(0编辑  收藏  举报