字符串思博题这一块还是有点薄弱啊。
Description
一个串T是S的循环节,当且仅当存在正整数k,使得S是T^k(即T重复k次)的前缀,比如abcd是abcdabcdab的循环节
。给定一个长度为n的仅由小写字符构成的字符串S,请对于每个k(1<=k<=n),求出S长度为k的前缀的最短循环节的
长度per_i。字符串大师小Q觉得这个问题过于简单,于是花了一分钟将其AC了,他想检验你是否也是字符串大师。
小Q告诉你n以及per_1,per_2,...,per_n,请找到一个长度为n的小写字符串S,使得S能对应上per。
Input
第一行包含一个正整数n(1<=n<=100000),表示字符串的长度。
第二行包含n个正整数per_1,per_2,...per_n(1<=per_i<=i),表示每个前缀的最短循环节长度。
输入数据保证至少存在一组可行解。
Output
输出一行一个长度为n的小写字符串S,即某个满足条件的S。
若有多个可行的S,输出字典序最小的那一个。
Sample Input
5
1 2 2 2 5
1 2 2 2 5
Sample Output
ababb
题目分析
暴力dfs
好吧考试时候暴力还是要保证写得又快又不挂的
时间复杂度$O(26^n)$。但是这个上界非常松,我不知道怎样是紧一点的下界。
暴力枚举
因为一定有解,那么每一个位置都枚举一种颜色看一看是否合法。
时间复杂度$n^2$。但是好像实际效率和dfs差了多少。
kmp
从枚举的思路顺着下去,算法的瓶颈在于找到一个最小的合法当前颜色。
我们知道如果存在最小循环节,那么其长度为$i-fail[i]$。于是从问题反面来看,只需要找到当前位置所有不可能的颜色,剩下的最小颜色就是其答案。那么求这个反面就相当于找$i$位置对应的所有合法的最小循环节。
所以,在这颗抽象的fail树上向前跳就可以得到答案。
还有一个处理的细节,就是$'a'$的特判。注意到$'a'$只会出现在$a[i]!=i(i!=1)$的位置。所以对于$a[i]==i$的位置,是不可能为$'a'$的,那么这里枚举答案时就要从$'b'$开始。
#include<bits/stdc++.h> const int maxn = 100035; bool vis[31]; int n,a[maxn],t[maxn]; int read() { char ch = getchar(); int num = 0; for (; !isdigit(ch); ch=getchar()); for (; isdigit(ch); ch=getchar()) num = (num<<1)+(num<<3)+ch-48; return num; } int main() { freopen("string.in","r",stdin); freopen("string.out","w",stdout); n = read(); for (int i=1; i<=n; i++) a[i] = read(); for (int i=2; i<=n; i++) { if (a[i]!=i) t[i] = t[(i-1)%a[i]+1]; else{ memset(vis, 0, sizeof vis); int j = i-1; while (j!=a[j]) j = (j-1)%a[j]+1, vis[t[j+1]] = 1; for (int k=1; k<26; k++) if (!vis[k]){ t[i] = k; break; } } } for (int i=1; i<=n; i++) putchar(t[i]+'a'); return 0; }
END