题意:
一个串T是S的循环节,当且仅当存在正整数k,使得S是T^k(即T重复k次)的前缀,比如abcd是abcdabcdab的循环节。给定一个长度为n的仅由小写字符构成的字符串S,请对于每个(1<=k<=n),求出S长度为k的前缀的最短循环节的长度per_i。字符串大师小Q觉得这个问题过于简单,于是花了一分钟将其AC了,他想检验你是否也是字符串大师。小Q告诉你 n n n以及 p e r 1 , p e r 2 , . . . , p e r n per_1,per_2,…,per_n per1,per2,...,pern,请找到一个长度为n的小写字符串S,使得S能对应上per。输出字典序最小的那一个。
思路:
KMP算法中的一个性质: i − F a i l [ i ] i-Fail[i] i−Fail[i]表示以i结尾的子串的最短循环节长度。所以 f a i l [ i ] = i − p e r i fail[i]=i-per_i fail[i]=i−peri
如果当前位置要推翻之前的循环节,即 f a i l [ i + 1 ] = 0 fail[i+1]=0 fail[i+1]=0,那么就沿着 f a i l fail fail指针往回跳,往回跳时遇到的所有字符都不能用,由于要输出字典序最小的那个,就贪心地选没有遇到的字典序最小的字符。
若 f a i l [ i + 1 ] ! = 0 fail[i+1]!=0 fail[i+1]!=0,那么当前字符就是 f a i l [ i + 1 ] fail[i+1] fail[i+1]那个位置的字符。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define MAXN 100010
int fail[MAXN],sta[MAXN],n;
char ans[MAXN];
int main()
{
scanf("%d",&n);
fail[0]=-1;
for(int i=1,x;i<=n;i++)
{
scanf("%d",&x);
fail[i]=i-x;
}
for(int i=0;i<n;i++)
if(fail[i+1])
{
ans[i]=ans[fail[i+1]-1];
int c=ans[i]-'a';
sta[i]=sta[fail[i]]|(1<<c);
}
else
{
int s,c;
if(fail[i]==-1) s=0;
else s=sta[fail[i]];
for(c=0;s&(1<<c);c++);
ans[i]=c+'a';
sta[i]=sta[fail[i]]|(1<<c);
}
printf("%s\n",ans);
}