目录
题面
题目描述
求一个字符串由多少个重复的子串连接而成。
例如 ababab
由三个 ab
连接而成,abcd
由 abcd
由一个 abcd
连接而成。
输入格式
输入多组数据
每一组数据仅一行,一个字符串 s(1≤∣s∣≤10^6)。
输入的结束标志为一个 .
输出格式
对于每一组数据,输出这组字符串由多少个重复的子串连接而成。
输入样例
abcd aaaa ababab .
输出样例
1 4 3
思路
本题用暴力方法绝对超时(看字符串长度嘛),因此使用字符串哈希。
- 用哈希将字符串转化为数字;
- 找字符串长度的因数(显然要组成字符串,必须是整串);
- 对于每个因数,判断是否合法(O(len)的复杂度,一段一段切出来判断是否相同)
补充
字符串哈希可以理解为进制转换,定义一个进制,每位乘以权值。而求出一段的长度只需类似前缀和的操作即可。具体将在代码中讲解。
而因为数字很大,我们需要取模。将哈希数组定义为 unsigned long long 即可,因为这样,程序会自动溢出(long long等会变为负数,因为有符号位),相当于取模。对于需要取模的题目,正常取模即可,不需要unsigned long long。
比赛时有的题可能会卡掉这种方法,可以自己记住几个不常用的取模的质数使用。
代码分段讲解
接下来将分段讲解代码,可以看注释来加深理解。
头文件及定义
#include<bits/stdc++.h>
#define M 1000005
#define ll long long
#define ull unsigned long long
using namespace std;
string s;
ull a,b,c,d,m,t,p=131,len,sum[M],power[M];
主函数
power[i]表示第i位的权值,预处理以便字符串哈希时使用。
定义p为一个大于最大字母ASCII码的质数,这样可以不用将字符转换为数字。p相当于这段代码执行后的结果是一个p进制数。
int main(){
power[0]=1;
for(int i=1;i<=1000010;i++)
power[i]=power[i-1]*p;//求每位的权值
while(1){
//依题目要求输入
cin>>s;
if(s==".")
return 0;
len=s.size();//求出字符串长度以便后续使用
//【核心代码】
sum[0]=s[0];
for(int i=1;i<len;i++)//组成数字
sum[i]=sum[i-1]*p+s[i];//类似一个前缀和
for(int i=1;i<=len;i++)//开始枚举字符串长度的因数
if(len%i==0){//找到一个因数
if(i==len){//到最后了,其它方法不行,只能由字符串自己组成了
printf("1\n");
break;
}
bool ret=count(i);//开始判断此长度的子串是否成立
if(ret)//此方案成立,由于是从最短的子串开始枚举的,因此子串个数最多,为最优答案,在函数内直接输出后退出
break;
}
}
return 0;
}
解释
这里对于【核心代码】的【组成数字】部分进行解释。
sum[i]=sum[i-1]*p+s[i]; 这里每次将上一次的结果乘以p,再加上这一位,类似前缀和和进制的结合体。
请看以下图片,假设p为10,即为十进制。
我们假设这里的字符串是一串数字123456.
(表格没空间了,标题中i后的数字代表sum数组的下标)
可见每次都是多加一位,如何取值我们在下面讲。
判断函数
bool count(ll Cut){//传入截取的长度
int cnt=0;
ull tmp=sum[Cut-1];//先截取一段当作基准
for(int i=Cut*2-1;i<len;i+=Cut){//从第二段开始截取
ll st=i-Cut;//算出截取的起点
ull nowCut=sum[i]-sum[st]*power[Cut];//截取一段
if(nowCut!=tmp)//这段与基准不同,不成立
return 0;
cnt++;//计算多一段
}
printf("%d\n",cnt+1);//开始那段没计算,+1
return 1;
}
解释
现在对于截取一段做出解释。
一般字符串哈希的截取公式为:sum[终点]-sum[起点-1]*power[终点-起点+1(即区间长度)].
这里的sum[终点]-sum[起点-1]其实就是计算前缀和,而乘以power即计算权值,这里就先不详细讲了。
完整程序
完整程序来啦~
#include<bits/stdc++.h>
#define M 1000005
#define ll long long
#define ull unsigned long long
using namespace std;
string s;
ull a,b,c,d,m,t,p=131,len,sum[M],power[M];
bool count(ll Cut){//传入长度
int cnt=0;
ull tmp=sum[Cut-1];//先切一段
for(int i=Cut*2-1;i<len;i+=Cut){//从第二段开始切
ll st=i-Cut;//切的起点
ull nowCut=sum[i]-sum[st]*power[Cut];//现在切的
if(nowCut!=tmp)//不是由这个组成
return 0;
cnt++;
}
printf("%d\n",cnt+1);
return 1;
}
int main(){
power[0]=1;
for(int i=1;i<=1000010;i++)
power[i]=power[i-1]*p;//p大于最大字母即可
while(1){
cin>>s;
if(s==".")
return 0;
len=s.size();
sum[0]=s[0];
for(int i=1;i<=len;i++)//组成数字
sum[i]=sum[i-1]*p+s[i];
for(int i=1;i<=len;i++){
if(len%i==0){//找到一个
if(i==len){
printf("1\n");
break;
}
bool ret=count(i);
if(ret)
break;
}
}
}
return 0;
}