KMP - 模板(循环节个数) - Period-HDU - 1358+Power Strings-POJ - 2406

KMP - 模板(循环节个数) - Period-HDU - 1358+Power Strings-POJ - 2406

一:Period-HDU - 1358

题目:

一个字符串的前缀是从第一个字符开始的连续若干个字符,例如”abaab”共有5个前缀,分别是a, ab, aba, abaa, abaab。

我们希望知道一个N位字符串S的前缀是否具有循环节。

换言之,对于每一个从头开始的长度为 i (i>1)的前缀,是否由重复出现的子串A组成,即 AAA…A (A重复出现K次,K>1)。

如果存在,请找出最短的循环节对应的K值(也就是这个前缀串的所有可能重复节中,最大的K值)。

输入格式
输入包括多组测试数据,每组测试数据包括两行。

第一行输入字符串S的长度N。

第二行输入字符串S。

输入数据以只包括一个0的行作为结尾。

输出格式
对于每组测试数据,第一行输出 “Test case #” 和测试数据的编号。

接下来的每一行,输出具有循环节的前缀的长度i和其对应K,中间用一个空格隔开。

前缀长度需要升序排列。

在每组测试数据的最后输出一个空行。

数据范围
2≤N≤1000000
输入样例:
3
aaa
4
abcd
12
aabaabaabaab
0
输出样例:
Test case #1
2 2
3 3

Test case #2

Test case #3
2 2
6 2
9 3
12 4

题意:

求 给 定 长 度 为 n 的 字 符 串 的 n 个 前 缀 中 , 长 度 为 i 的 “ 循 环 节 ” , 以 及 “ 循 环 节 ” 的 个 数 n [ i ] , 1 < = i < = n 2 。 求给定长度为n的字符串的n个前缀中,长度为i的“循环节”,以及“循环节”的个数n[i],1<=i<=\frac{n}{2}。 nnin[i]1<=i<=2n

样 例 : a a b a a b a a b a a b 长 度 为 2 的 前 缀 " a a " 的 循 环 节 为 a , 有 2 个 。 长 度 为 6 的 前 缀 " a a b a a b " 的 循 环 节 为 a a b , 有 2 个 。 长 度 为 9 的 前 缀 " a a b a a b a a b " 的 循 环 节 为 a a b , 有 3 个 。 长 度 为 12 的 前 缀 " a a b a a b a a b a a b " 的 循 环 节 为 a a b , 有 4 个 。 样例:aabaabaabaab\\长度为2的前缀"aa"的循环节为a,有2个。\\长度为6的前缀"aabaab"的循环节为aab,有2个。\\长度为9的前缀"aabaabaab"的循环节为aab,有3个。\\长度为12的前缀"aabaabaabaab"的循环节为aab,有4个。 aabaabaabaab2"aa"a26"aabaab"aab29"aabaabaab"aab312"aabaabaabaab"aab4

题解:

首 要 问 题 是 如 何 找 出 循 环 节 。 首要问题是如何找出循环节。

通 过 K M P 算 法 : 通过KMP算法: KMP
在这里插入图片描述
K M P 算 法 中 : 数 组 N e x t [ j ] 的 含 义 指 前 j − 1 个 字 符 构 成 的 子 串 的 最 长 相 同 前 后 缀 的 长 度 , 设 N e x t [ j ] = l 。 KMP算法中:数组Next[j]的含义指前j-1个字符构成的子串的最长相同前后缀的长度,设Next[j]=l。 KMPNext[j]j1Next[j]=l

那 么 可 知 红 色 部 分 前 缀 与 蓝 色 部 分 后 缀 长 度 相 等 , 都 为 l 。 从 而 可 知 绿 色 圈 圈 部 分 的 两 条 线 段 长 度 相 等 。 那么可知红色部分前缀与蓝色部分后缀长度相等,都为l。从而可知绿色圈圈部分的两条线段长度相等。 l绿线

下 面 一 根 线 段 是 红 色 部 分 前 缀 , 将 其 平 移 , 每 一 段 均 与 蓝 色 后 缀 部 分 对 应 相 等 , 下面一根线段是红色部分前缀,将其平移,每一段均与蓝色后缀部分对应相等, 线

即 2 = 5 , 3 = 6 , 4 = 7 , 由 1 = 5 知 1 = 2 , 由 2 = 6 知 2 = 3 , 由 3 = 7 知 3 = 4 , 所 以 1 = 2 = 3 = 4 , 1 , 2 , 3 , 4 即 该 字 符 串 的 循 环 节 。 即2=5,3=6,4=7,由1=5知1=2,由2=6知2=3,由3=7知3=4,所以1=2=3=4,\\1,2,3,4即该字符串的循环节。 2=53=64=71=51=22=62=33=73=41=2=3=41234

于 是 前 j − 1 个 子 串 的 循 环 节 的 最 小 长 度 就 是 j − N e x t [ j ] , 这 必 然 是 最 小 的 循 环 节 , 这 是 因 为 N e x t [ j ] 数 组 存 储 的 是 相 同 前 后 缀 的 最 大 长 度 。 于是前j-1个子串的循环节的最小长度就是j-Next[j],这必然是最小的循环节,\\这是因为Next[j]数组存储的是相同前后缀的最大长度。 j1jNext[j]Next[j]

循 环 节 的 个 数 即 : 子 串 长 度 i / 循 环 节 长 度 t 。 循环节的个数即: 子串长度i/循环节长度t。 i/t

注 意 : 判 定 是 否 为 循 环 节 还 需 判 断 t 能 否 整 除 i , 若 不 能 , 则 说 明 不 能 形 成 循 环 节 。 注意:判定是否为循环节还需判断t能否整除i,若不能,则说明不能形成循环节。 ti


代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
const int N=1e6+10;
int Next[N];
char str[N];
int n;

void get_next()
{
    for(int i=2,j=0;i<=n;i++)
    {
        while(j&&str[i]!=str[j+1]) j=Next[j];
        if(str[i]==str[j+1]) j++;
        Next[i]=j;
    }
}
int main()
{
    int T=1;
    while(~scanf("%d",&n),n)
    {
        scanf("%s",str+1);
        
        get_next();

        printf("Test case #%d\n",T++);
        for(int i=2;i<=n;i++)
        {
            int t = i - Next[i];
            if (i > t && i % t == 0) printf("%d %d\n", i, i / t);   ///判断条件
        }
        puts("");
    }

    return 0;
}

下标从0开始的Next数组求法:

写 法 ① 、 下 标 从 0 开 始 的 n e x t 数 组 对 应 为 下 标 从 1 开 始 的 n e x t 数 组 的 值 − 1 。 写法①、下标从0开始的next数组对应为下标从1开始的next数组的值-1。 0next1next1

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
const int N=1e6+10;
int ne[N];
char str[N];
int n;

void get_next()
{
    ne[0]=-1;
    for(int i=1,j=-1;i<n;i++)
    {
        while(j>=0&&str[i]!=str[j+1]) j=ne[j];
        if(str[i]==str[j+1]) j++;
        ne[i]=j;
    }
}

int main()
{
    int T=1;
    while(~scanf("%d",&n),n)
    {
        scanf("%s",str);

        get_next();

        printf("Test case #%d\n",T++);
        for(int i=1;i<n;i++)
        {
            int t = i + 1 - (ne[i]+1);   //i+1是前缀长度,ne[i]+1是最大共同前后缀的长度
            if ((i+1)/t > 1 && (i+1) % t == 0) printf("%d %d\n", i+1, (i+1) / t);
        }
        puts("");
    }

    return 0;
}

写 法 ② 、 对 应 的 n e x t 数 组 的 下 标 仍 然 从 1 开 始 写法②、对应的next数组的下标仍然从1开始 next1

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
const int N=1e6+10;
int ne[N];
char str[N];
int n;

void get_next(char *str)
{
    int i=0,j=-1,n=strlen(str);
    ne[i]=j;
    while(i<n){
        if(j==-1 || str[i]==str[j]) ne[++i]=++j;
        else j=ne[j];
    }
}

int main()
{
    int T=1;
    while(~scanf("%d",&n),n)
    {
        scanf("%s",str);

        get_next(str);

        printf("Test case #%d\n",T++);
        for(int i=2;i<=n;i++)
        {
            int t = i - ne[i];
            if (i % t == 0 && i/t > 1) printf("%d %d\n", i, i/ t);   ///判断条件
        }
        puts("");
    }

    return 0;
}

二:Power Strings-POJ - 2406

题目:
假设s可以由t重复k次拼成,即s=tttt……tt,我们称为s=tk。先给定一个字符串s,求最大的n使得存在t满足s=tn

Input
多组数据,每行一个字符串(仅包含可打印字符且长度不超过1000000),以单独一行.作为终结标志

Output
每组数据一行答案

Sample Input
abcd
aaaa
ababab
.

Sample Output
1
4
3

题解:

如 上 题 , 唯 一 的 区 别 在 于 母 串 无 循 环 节 的 情 况 下 , 整 个 字 符 串 视 作 一 个 “ 循 环 节 ” , 输 出 “ 1 ” 即 可 。 如上题,唯一的区别在于母串无循环节的情况下,整个字符串视作一个“循环节”,输出“1”即可。 1


代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#define ll long long
#define inf 0x7fffffff
using namespace std;
const int N=1e6+10;
int m;
char p[N];
int Next[N];

void get_next()
{
    for(int i=2,j=0;i<=m;i++)
    {
        while(j&&p[i]!=p[j+1]) j=Next[j];
        if(p[i]==p[j+1]) j++;
        Next[i]=j;
    }
}

int main()
{
    while(scanf("%s",p+1),p[1]!='.')
    {
        m=strlen(p+1);

        get_next();

        int len=m-Next[m];
        if(m%len==0) printf("%d\n",m/len);
        else printf("1\n");
    }

    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值