Power Strings
题面翻译
题意简述:
求一个字符串由多少个重复的子串连接而成。
例如 ababab
由三个 ab
连接而成,abcd
由 abcd
由一个 abcd
连接而成。
输入格式
本题多组数据。
每一组数据仅有一行,这一行仅有一个字符串 s s s。
输入的结束标志为一个 .
。
输出格式
对于每一组数据,输出这组字符串由多少个重复的子串连接而成。
说明/提示
1 ≤ ∣ s ∣ ≤ 1 0 6 1\le |s|\le 10^6 1≤∣s∣≤106。
题目描述
输入格式
输出格式
样例 #1
样例输入 #1
abcd
aaaa
ababab
.
样例输出 #1
1
4
3
读题
采用hash做法,通过哈希值来判断字符串中是否存在重复子串,并求出重复子串的个数。
此题较优秀,笔者在此想分为两部分来谈论,具体分为总览
与分析
,当然,在阅读后有兴趣的读者可以通过网络,查阅相关资料(比如蓝书–《算法竞赛入门经典·训练指南》)。
1.总览
根据题目,很容易想到:
- 读入一个字符串,存储在数组
aa
中,作为主串。 - 检查是否为结束标志,即字符串以句号
'.'
开头。如果是,则退出程序;否则继续执行后续步骤。 - 计算输入字符串
aa
的长度,存储在变量n
中。 - 初始化哈希数组
h
,数组大小设为M
。 - 计算哈希数组
h
,其中h[i]
表示字符串aa
的前缀子串aa[1...i]
的哈希值。 - 进入循环遍历,枚举每个可能的重复模式长度
i
。 - 判断当前长度
i
是否能整除字符串长度n
,如果不能,则继续下一次循环。 - 计算模式串的哈希值
s
,即子串aa[1...i]
的哈希值。 - 调用
check
函数,检查字符串是否存在长度为i
的重复模式。check
函数使用哈希值进行匹配判断。 - 如果存在重复模式,则输出重复模式的个数,即字符串长度除以重复模式长度。
- 回到第 5 步,继续处理下一个输入字符串。
代码
#include<bits/stdc++.h>
using namespace std;
const int x=1311,M=1e7;
int a[M],b[M],h[M],f=0;
char aa[M],bb[M];
int n,s;
bool init(){
scanf("%s",aa+1);
if(aa[1]=='.') return 0;
n=strlen(aa+1);
h[0]=0;
for (int i=1;i<=n;i++) h[i]=h[i-1]*x+int(aa[i]-'A'+1);
return 1;
}
bool check(int v,int k){
for (int i=1;i<=n;i+=k)
if (v!=h[i+k-1]-h[i-1]*a[k]) return 0;
return 1;
}
int main(){
a[0]=1;
for (int i=1;i<=1000000;i++) a[i]=a[i-1]*x;
while (init()){
for (int i=1;i<=n;++i){
if (n%i) continue;
if (i>n/2){cout<<1<<endl;break;}
s=h[i];
if (check(s,i)){
cout<<n/i<<endl;
break;
}
}
}
return 0;
}
2.分析
相信读者已经看完总览和代码了,接下来是分析,心急的读者可能想跳过了,但笔者十分希望认真看完,因为它实在是太优美,太巧妙了:
check
bool check(int v,int k){
for (int i=1;i<=n;i+=k)
if (v!=h[i+k-1]-h[i-1]*a[k]) return 0;
return 1;
}
函数 check
被用于判断字符串是否存在指定长度的重复子串。笔者喜欢封装函数。
- 通过循环遍历,以定长度等于
k
的方式检查是否存在hash值为v
的重复子串。 - 检查当前位置子串的哈希值是否等于预先计算的目标哈希值
v
。如果不相等,则说明不是重复子串,返回false
。 - 如果循环结束后都没有找到不匹配的情况,则说明字符串一定存在长度为
k
的重复子串,返回true
。
init
bool init(){
scanf("%s",aa+1);
if(aa[1]=='.') return 0;
n=strlen(aa+1);
h[0]=0;
for (int i=1;i<=n;i++) h[i]=h[i-1]*x+int(aa[i]-'A'+1);
return 1;
}
总览中有许多操作,很多都是初始化,我们可以使用do-while
,但笔者认为不如使用函数封装下。
需要注意下hash值的递推计算公式,在上一题解中,已做阐述,笔者在此不过多赘述。
附加代码
#include <iostream>
#include <string>
using namespace std;
int countRepeatingSubstrings(string s) {
int n = s.length();
for (int i = 1; i <= n / 2; i++) {
if (n % i == 0) {
bool isRepeating = true;
for (int j = i; j < n; j++) {
if (s[j] != s[j % i]) {
isRepeating = false;
break;
}
}
if (isRepeating) {
return n / i;
}
}
}
return 1;
}
int main() {
string s;
cin >> s;
while (s != ".") {
int repeatingCount = countRepeatingSubstrings(s);
cout << repeatingCount << endl;
cin >> s;
}
return 0;
}