【bzoj3325】[Scoi2013]密码 逆模拟Manacher
题目描述
给出一个只包含小写字母的字符串的长度、以每一个字符为中心的最长回文串长度、以及以每两个相邻字符的间隙为中心的最长回文串长度,求满足条件的字典序最小的字符串。
输入
输入由三行组成。
第一行仅含一个整数N,表示密码的长度。
第二行包含N 个整数,表示以每个字符为中心的最长回文串长度。
第三行包含N - 1 个整数,表示每两个相邻字符的间隙为中心的最长回文串长度。
1 <= n <= 10^5。
输出
输出仅一行。输出满足条件的最小字典序密码。古籍中的信息是一定正确的,故一定存在满足条件的密码。
样例输入
Sample #1
3
1 1 1
0 0
Sample #2
3
1 3 1
0 0
Sample #3
3
1 3 1
2 2
样例输出
Sample #1
abc
Sample #2
aba
Sample #3
aaa
题解
逆模拟Manacher
本题和 bzoj4974 类似,只不过变成了回文串长度。
那么可以考虑逆模拟Manacher算法的过程:从上一个已经确定的回文串位置开始,到当前回文半径结束,这些串都满足回文串的性质,所以后面的字符可以直接由前面的字符确定。
而如果当一个字符没有确定,此时的情况较那题更难处理——一个位置结尾的所有回文串。
我们不妨换一个思路:当一个位置达到最大回文长度时,说明下一个长度不再满足回文串的性质。因此可以直接对于每个位置开一个大小为26的桶,当达到最大回文长度时,就让后面的下一个字符与前面的上一个字符标记为不同。当一个字符没有确定时,就在其对应的桶中找到字典序最小的字符,设为当前字符。
时间复杂度$O(26n)$。
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; char str[200010]; int p[200010] , vis[200010][26]; int main() { int n , i , j , mx = 0 , last; scanf("%d" , &n); for(i = 1 ; i <= n ; i ++ ) scanf("%d" , &p[i * 2 - 1]); for(i = 1 ; i < n ; i ++ ) scanf("%d" , &p[i * 2]); str[0] = '#'; for(i = 1 ; i <= n * 2 - 1 ; i ++ ) { if(mx < i) { for(j = 0 ; j < 26 ; j ++ ) if(!vis[i][j]) break; str[i] = j + 'a'; } for(j = (mx >= i ? min(mx - i + 1 , p[2 * last - i]) : 1) ; j <= p[i] ; j ++ ) str[i + j] = str[i - j]; if(i > p[i]) vis[i + p[i] + 1][str[i - p[i] - 1] - 'a'] = 1; if(i + p[i] > mx) mx = i + p[i] , last = i; } for(i = 1 ; i < n * 2 ; i += 2) putchar(str[i]); printf("\n"); return 0; }