kmp周期性循环节(Cyclic Nacklace HDU - 3746)

这篇博客探讨了如何使用Next数组来处理字符串的循环性质。首先解释了Next数组的概念,即它用于找到字符串的最小循环节。接着,通过两个编程题目展示了如何运用这个概念。题目1要求在不改变字符串内部结构的情况下,找到构建循环字符串所需的最少附加字符数。题目2则要求计算给定字符串的循环次数。当字符串长度能被Next数组计算出的循环节整除时,循环次数为字符串长度除以循环节;否则,循环次数为1。这两个题目都强调了Next数组在字符串处理中的应用。

知识点:考察对next数组的理解:周期性字符串循环节长度 ans 是 n−next[n] ,也可以理解为 n-next 的前缀是最小覆盖子串 ,而当前提为n% ans==0&&next[n] !=0时,循环次数是 n/ans 。

(一般只用到next,不用kmp)

题目1

题意:给你一串字符串。可以在这个字符串左右加上任意字符,使最后求得的字符串可以构成循环。

思路:

用前缀数组可以求得初始字符串的最小循环节。
最小循环节=(原字符串长度-末尾前缀数组值)
然后进行讨论:

如果最小循环节=原子符串长度.说明末尾前缀数组值为0.说明原串中,只能通过复制一次原串得到目标字符串。 ans=原串长度
如果原串长度%最小循环节=0.说明可以通过循环原串中某前缀就可以构成原串,原串已经完成循环。 ans=0
如果不是上面两种情况。说明原串中,有部分不在循环中。可以求得这部分长度为最小循环节-原串长度%最小循环节。我们加上这部分就可以构成循环,满足条件。** ans=最小循环节-原串长度%最小循环节
 

#include<bits/stdc++.h>
#define ll long long
#define R register int
#define inf 0x3f3f3f3f
#define mod 1000000007;
#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
inline ll read(){
   ll s=0,w=1;
   char ch=getchar();
   while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
   while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
   return s*w;
}
void put1(){ puts("YES") ;}
void put2(){ puts("NO") ;}
void put3(){ puts("-1"); }

const int manx=1e5+5;
ll nexts[manx];
ll n,m,ans,t;
string p,s;

void getnexts()
{
    int i=0,j=-1;
    nexts[i]=j;
    while(i<m){
        if(j==-1 || p[i]==p[j]) nexts[++i]=++j;
        else j=nexts[j];
    }
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>t;
    while(t--)
    {
        cin>>p;
        m=p.size();
        getnexts();
        ll len=m-nexts[m];
        if(len!=m && m%len==0) cout<<0<<endl;
        else cout<<len-m%len<<endl;
    }
    return 0;
}

题目2

题意:给一个字符串,求循环个数

#include<string>
#include<iostream>
#include<cstdio>
#define ll long long
#define R register int
#define inf 0x3f3f3f3f
#define mod 1000000007;
#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
inline ll read(){
   ll s=0,w=1;
   char ch=getchar();
   while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
   while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
   return s*w;
}
void put1(){ puts("YES") ;}
void put2(){ puts("NO") ;}
void put3(){ puts("-1"); }

const int manx=1e6+5;
ll nexts[manx];
ll n,m,ans,t;
string p,s;

void getnexts()
{
    int i=0,j=-1;
    nexts[i]=j;
    while(i<m){
        if(j==-1 || p[i]==p[j]) nexts[++i]=++j;
        else j=nexts[j];
    }
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    while(cin>>p)
    {
        if(p[0]=='.') break;
        m=p.size();
        getnexts();
        ll len=m-nexts[m];
        if(len!=m && m%len==0 ) cout<<m/len<<endl;
        else cout<<1<<endl;
    }
    return 0;
}

KMP算法的核心在于构建部分匹配表(也称为`next`数组),该表用于记录模式串的部分匹配信息。通过这些信息,可以在寻找重复子串的过程中优化查找效率。 要利用KMP算法来找出字符串的最小循环节,可以通过以下方法实现: ### 使用 KMP 找到最小循环节 对于一个长度为 `n` 的字符串 `s`,如果存在某个最小子串 `p` 能够通过多次复制构成原字符串,则这个子串即是最小循环节。以下是具体过程: #### 构建 Next 数组 定义一个辅助函数计算前缀函数值并存储于 next 数组中。此操作的时间复杂度为 O(n)[^1]。 ```python def compute_next_array(pattern): n = len(pattern) next_arr = [0] * n j = 0 for i in range(1, n): while j > 0 and pattern[i] != pattern[j]: j = next_arr[j - 1] if pattern[i] == pattern[j]: j += 1 next_arr[i] = j return next_arr ``` #### 计算最小循环节 基于上述得到的 next 数组,我们可以进一步推导出最小周期 t=n−next[n−1] 。当满足条件 (t 可整除 n),则表明 s 存在一个大小为 t 的基本单元反复拼接而成;否则整个字符串本身不具备更短的有效循环结构。 ```python def find_smallest_repeating_substring(s): next_arr = compute_next_array(s) length_of_string = len(s) period = length_of_string - next_arr[-1] if length_of_string % period == 0 and period != length_of_string: return s[:period] else: return s ``` 以上代码片段展示了如何应用 KMP 中的 next 数组特性去检测是否存在以及返回目标字符串内的最简重复序列。 ### 结论 综上所述,借助 KMP 算法不仅可以高效解决模式匹配问题,在特定场景下还能巧妙运用于诸如发现字符串内部隐藏规律的任务之中,比如本例中的定位最小循环单位即是如此。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值