目录
Seek the Name, Seek the Fame
要求:
总时间限制: 2000ms
内存限制: 65536kB
描述:
The little cat is so famous, that many couples tramp(长途跋涉) over hill and dale(山谷) to Byteland, and asked the little cat to give names to their newly-born babies. They seek the name, and at the same time seek the fame. In order to escape from such boring job, the innovative little cat works out an easy but fantastic algorithm:
Step1. Connect the father's name and the mother's name, to a new string S.
Step2. Find a proper prefix-suffix string of S (which is not only the prefix, but also the suffix of S).
Example: Father='ala', Mother='la', we have S = 'ala'+'la' = 'alala'. Potential prefix-suffix strings of S are {'a', 'ala', 'alala'}. Given the string S, could you help the little cat to write a program to calculate the length of possible prefix-suffix strings of S? (He might thank you by giving your baby a name:)
输入:
The input contains a number of test cases. Each test case occupies a single line that contains the string S described above.
Restrictions: Only lowercase letters may appear in the input. 1 <= Length of S <= 400000.
输出:
For each test case, output a single line with integer numbers in increasing order, denoting the possible length of the new baby's name.
样例输入:
ababcababababcabab aaaaa
样例输出:
2 4 9 18 1 2 3 4 5
思路分析:
这一道题是经典的KMP模式匹配算法的派生题目. 目标是对于给定的一个字符串S,求出其所有的 prefix-suffix string(前后缀字符串)的长度. 前后缀字符串意思是说这个字符串既是原字符串的前缀,也是原字符串的后缀.
我们不难发现,经典的(未经优化)KMP算法计算出的特征向量(需要多加一位),会求出最长的一个S',使得S'既是S的前缀,也是S的后缀. 与题目进行对应,题目希望我们求出所有的前后缀字符串,但是经典的KMP算法只能让我们求出最长的(不包含原字符串).
所以我们怎么计算出剩下的前后缀字符串呢?这需要我们对KMP模式匹配算法有更深的理解. 当我们求出来最长的前后缀字符串S'长度k后,接下来更短的前后缀字符串一定是S'的前后缀字符串,那么我们就因此建立了一个递归关系.
这里的递归关系不需要我们重新再算一次特征向量,因为其特征向量在我们第一次计算字符串S的时候已经计算过了,我们只需要在原特征向量(next数组)中引用其值即可.(具体细节参见最终代码)
最终代码:
#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
/* 这是KMP模式匹配算法,如果不理解建议先专门学习一下KMP */
void kmp(char *str, int *next){
int k1=-1;
int k2=0;
next[0]=-1;
int len=strlen(str);
while(k2!=len){
while(k1>=0&&str[k2]!=str[k1]){
k1=next[k1];
}
k1++;
k2++;
/* 我们在特征向量里多加了一位,所以不需要if判断break */
next[k2]=k1;
}
}
int main()
{
char str[400005];
vector<int> N;
while(cin>>str){
getchar();// Optional!!!
int len=strlen(str);
int next[len+1];
kmp(str,next);
N.push_back(len);// Don't forget!!!
/* 这里就是关键所在 */
while(next[len]){
N.push_back(next[len]);
len=next[len];//递归关系(最长前后缀子字符串的最长前后缀子字符串)
}
/* 我们在vector的添加顺序与最终输出是相反的,用rbegin和rend来输出 */
printf("%d",*N.rbegin());
for(auto it=N.rbegin()+1;it!=N.rend();it++){
printf(" %d",*it);
}
printf("\n");
N.clear();
}
}