题目链接:传送门
题意描述:
我们定义翻转的操作:把一个串以最后一个字符作对称轴进行翻转复制。形式化地描述就是,如果他翻转的串为 RR,那么他会将前 |R|−1|R|−1个字符倒序排列后,插入到串的最后。举例而言,串'abcd'进行翻转操作后,将得到'abcdcba';串'qw'连续进行 22次翻转操作后,将得到'qwqwq';串'z'无论进行多少次翻转操作,都不会被改变。现在,我们知道了一个串翻转后的的前缀,要你求出这个串的在这个前缀中的翻转位置。
题目分析:
首先,我们来看看只翻转一次后,就能得到答案的情况,就是如果某个位置的回文长度能到达这个字符串的末尾,那这个位置肯定能做翻转位置的,但是这种情况出现的位置只能在后半部分。但如果是翻转多次的话,那么位置只能出现在前半部分,我们只要保证在左半部分的位置的回文串长度左边能到达字符串的开头,并且右边的位置能满足翻转条件的话,那么,这个位置也是答案。快速判断字符串中某个位置的长度的话可以套Manacher模板,时间复杂度为O(n)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e6+5;
int t;
char s[maxn], Ma[maxn];
int Mp[maxn], vis[maxn];
void Manacher(char s[], int len){
int l = 0;
Ma[l++] = '$';
Ma[l++] = '#';
for(int i=0;i<len;++i){
Ma[l++] = s[i];
Ma[l++] = '#';
}
Ma[l] = 0;
int mx = 0, id = 0;
for(int i=0;i<l;++i){
Mp[i] = mx>i? min(Mp[2*id-i], mx-i):1;
while(Ma[i+Mp[i]] == Ma[i-Mp[i]]) Mp[i]++;
if(i + Mp[i] > mx){
mx = i+Mp[i];
id = i;
}
}
}
int main(){
scanf("%d", &t);
while(t--){
scanf("%s", s);
int len = strlen(s);
/*
for(int i=0;i<len;++i){
vis[i] = 0;
}*/
vis[len] = 1;
Manacher(s, len);
int temp;
// for(int i=0;i<2*len+2;++i) cout << Mp[i] << endl;
for(int i=2*len-2;i>=2;i-=2){
vis[i/2] = 0;
temp = (Mp[i]-1+i)/2;
// cout << (Mp[i]-1+i)/2 << endl;
if(temp == len) vis[i/2] = 1;
if(vis[temp] && Mp[i] == i){
vis[i/2] = 1;
}
}
for(int i=1;i<len;++i){
if(vis[i]) printf("%d ", i);
}
printf("%d\n", len);
}
return 0;
}