题意:给一个字符串S,定义一种操作为:从字符串首位置(0)开始将S0~Si的字符移到末尾得到一个新的字符串,直到每一个位置都遍历完成,如:
abab—> (0开始)abab —>(0~1位置) baba —>(0~2位置) abab —>(0~3位置)baba
所有得到的新的字符串从0~n编号,然后将完全一样的字符串分到一个组里面,要求输出有几个组,和每个组里面有几个字符串,每个字符串的编号。
题解:题意读完可能是有点懵的,但是仔细想一下,如果当前这个位置正好是这个字符串的循环节末尾部分那么将它之前的移到末尾是不是相当于没有改变这个字符串,还是一样的。那么顺着这个思路容易想到,最后分组以后的组数就是循环节的长度,如果当前字符串没有循环节的话,那在每一个位置进行移动都会得到不同的字符串,而,每一组的大小就是整个字符串循环节的个数。接下来就是每一组里面每个字符串的编号,当然如果字符串中没有循环节,每个字符串的编号就是它移动的位置的编号,而有循环节的话,每个组里面的第一个字符串的编号一定是在第一个循环节里面的它的首字母的位置,以后就用首字母的位置加上轮了几个循环节的长度就行了。
顺便补充一下kmp的next数组求循环节的问题:
在kmp算法中next[i]表示的是si这个字符前面的最长公共前缀和后缀的长度,因为字符串中字符的下标是从0开始,所以next[i]也相当于到第i个字符为止的最长公共前缀和后缀。那么i-next[i]就表示,长度为i的字符串和后缀相同的最长前缀,如果剩下的后缀可以被字符串长度 i 整除,那么就表示,当前这个字符串是由几段重复的字串组成的,那就是我们所谓的循环节了。所以利用kmp的next数组判断字符串循环节的写法就是:
int lenn=i-next[i];//i表示字符串中的i位置
if(i!=lenn&&i%lenn==0)//i!=lenn排除的是最长公共前后缀为0,剩下的整个部分也可以被自己整除的情况
循环节长度=lenn,循环节个数=i/lenn
附上代码:
#include<bits/stdc++.h>
using namespace std;
char s[100010];
int nextt[100010];
void get_next()
{
nextt[0]=-1;
int i=0;
int k=-1;
int len=strlen(s);
while(i<len)
{
if(k==-1||s[i]==s[k])
{
i++;
k++;
nextt[i]=k;
}
else k=nextt[k];
}
}
int main()
{
scanf("%s",s);
memset(nextt,0,sizeof(nextt));
get_next();
int len=strlen(s);
int lenn=len-nextt[len];
int ans=len%lenn==0?lenn:1;//判断整个字符串是不是有循环节
int kind=len/lenn;//循环节个数,也就是每组里的成员个数
printf("%d\n",ans);//循环节长度,也就是可以分的组数
for(int i=0;i<ans;i++)
{
printf("%d ",kind);
for(int j=0;j<kind;j++)
{
printf("%d ",i+j*ans);
}
printf("\n");
}
return 0;
}