Advertisement

Power Strings

阅读量:

Power Strings

题面翻译

题意简述:

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

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

输入格式

本题多组数据

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

输入的结束标志为一个 .

输出格式

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

说明/提示

1\le |s|\le 10^6

题目描述

PDF

输入格式

输出格式

样例 #1

样例输入 #1

复制代码
    abcd
    aaaa
    ababab
    .

样例输出 #1

复制代码
    1
    4
    3

读题

基于哈希技术的方法中提到,在分析给定字符串时会利用哈希值来识别字符串中的重复子串是否存在,并计算所有重复子串的数量

此题表现优异, 笔者打算将内容分为两个部分进行阐述, 具体包括概述与深入分析. 当然, 在阅读之后有興趣的讀者可以通过網路查閱相關資料(例如《算法競賽入門經典·訓練指南》).

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<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 后进行检测,以查看是否存在具有哈希值为 v 的重复子串。
计算当前位置子串的哈希值时,请与预先计算的目标哈希值进行比较:如果当前哈希值与目标哈希值不一致,则可确定此位置不满足条件。
如果在整个遍历过程中均未发现任何不符合条件的情况,则可判定该字符串必定包含至少一个长度为 k 的重复子串。

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值的递推计算公式的具体实现细节.上一篇已经进行了详细阐述因此在此不做进一步讨论.

附加代码1

复制代码
    #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;
    }

附加代码2

复制代码
    #include<bits/stdc++.h>
    const int M=1e7; 
    char a[M];int n;
    using namespace std;
    int f[M],nxt[M];long long ans;
    void getfail(){
    	int j=0;
    	f[0]=f[1]=0;
    	for (int i=1;i<n;i++){
    		while(j and a[i+1]!=a[j+1]) j=f[j];
    		if (a[i+1]==a[j+1]) j++;
    		f[i+1]=j; 
    	}
    }
    int main(){
    	while (scanf("%s",a+1)==1){
    		if (a[1]=='.') break;
    		n=strlen(a+1);
    		getfail();
    		if (n%(n-f[n])==0) cout<<n/(n-f[n])<<endl;
    		else cout<<1<<endl;
    	}
    	return 0;
    }

全部评论 (0)

还没有任何评论哟~