【思维题 kmp 构造】bzoj4974: [Lydsy1708月赛]字符串大师

字符串思博题这一块还是有点薄弱啊。

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

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

原文地址:https://www.cnblogs.com/antiquality/p/9806707.html