UVA10298 Power Strings(字符串哈希)

目录

题面

题目描述

输入格式

输出格式

思路

补充 

 代码分段讲解

头文件及定义

主函数

解释

判断函数

解释

完整程序


题面

题目描述

求一个字符串由多少个重复的子串连接而成。

例如 ababab 由三个 ab 连接而成,abcd 由 abcd 由一个 abcd 连接而成。

输入格式

输入多组数据

每一组数据仅一行,一个字符串 s(1≤∣s∣≤10^6)。

输入的结束标志为一个 .

输出格式

对于每一组数据,输出这组字符串由多少个重复的子串连接而成。

输入样例

abcd
aaaa
ababab
.

输出样例

1
4
3

思路

本题用暴力方法绝对超时(看字符串长度嘛),因此使用字符串哈希。

  1. 用哈希将字符串转化为数字;
  2. 找字符串长度的因数(显然要组成字符串,必须是整串);
  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;
}
  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值