BZOJ4974 字符串大师【KMP】【贪心】

题意:

一个串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] iFail[i]表示以i结尾的子串的最短循环节长度。所以 f a i l [ i ] = i − p e r i fail[i]=i-per_i fail[i]=iperi
如果当前位置要推翻之前的循环节,即 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);
}
    原文作者:KMP算法
    原文地址: https://blog.csdn.net/qq_41343943/article/details/83380906
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞