Power Strings

Power Strings

题面翻译

题意简述:

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

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

输入格式

本题多组数据

每一组数据仅有一行,这一行仅有一个字符串 s s s

输入的结束标志为一个 .

输出格式

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

说明/提示

1 ≤ ∣ s ∣ ≤ 1 0 6 1\le |s|\le 10^6 1s106

题目描述

PDF

Power Strings_第1张图片

输入格式

输出格式

样例 #1

样例输入 #1

abcd
aaaa
ababab
.

样例输出 #1

1
4
3

读题

采用hash做法,通过哈希值来判断字符串中是否存在重复子串,并求出重复子串的个数。

此题较优秀,笔者在此想分为两部分来谈论,具体分为总览分析,当然,在阅读后有兴趣的读者可以通过网络,查阅相关资料(比如蓝书–《算法竞赛入门经典·训练指南》)。

1.总览

根据题目,很容易想到:

  1. 读入一个字符串,存储在数组 aa 中,作为主串。
  2. 检查是否为结束标志,即字符串以句号 '.' 开头。如果是,则退出程序;否则继续执行后续步骤。
  3. 计算输入字符串 aa 的长度,存储在变量 n 中。
  4. 初始化哈希数组 h,数组大小设为 M
  5. 计算哈希数组 h,其中 h[i] 表示字符串 aa 的前缀子串 aa[1...i] 的哈希值。
  6. 进入循环遍历,枚举每个可能的重复模式长度 i
  7. 判断当前长度 i 是否能整除字符串长度 n,如果不能,则继续下一次循环。
  8. 计算模式串的哈希值 s,即子串 aa[1...i] 的哈希值。
  9. 调用 check 函数,检查字符串是否存在长度为 i 的重复模式。check 函数使用哈希值进行匹配判断。
  10. 如果存在重复模式,则输出重复模式的个数,即字符串长度除以重复模式长度。
  11. 回到第 5 步,继续处理下一个输入字符串。

代码

#include
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 被用于判断字符串是否存在指定长度的重复子串。笔者喜欢封装函数。

  1. 通过循环遍历,以定长度等于 k 的方式检查是否存在hash值为v的重复子串。
  2. 检查当前位置子串的哈希值是否等于预先计算的目标哈希值 v。如果不相等,则说明不是重复子串,返回 false
  3. 如果循环结束后都没有找到不匹配的情况,则说明字符串一定存在长度为 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 
#include 
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;
}

你可能感兴趣的:(算法)