【题解】Luogu P5446 [THUPC 2018] 绿绿和串串
思路
考虑什么情况下 \(S\) 为最终 \(R\) 的前缀。第一种情况, \(S\) 的长度小于一次翻转后 \(R\) 的长度,这时候我们就需要找包含末尾的回文串,相当于初始 \(R\) 在此串的回文中心处截止,一次翻转后,\(S\) 只截取到初始 \(R\) 的一部分回文。第二种情况,\(S\) 的长度大于一次翻转,那么必然可以通过若干次翻转到达串尾或一个截取不完全的反转初始 \(R\),也就是第一种情况,从后往前依次查找回文串,如果他们依次包含(即前串末尾是后串回文中心)且第一个回文串的开头是 \(S\) 的第一位,那么从第一位到第一个回文串的回文中心也可以是初始 \(R\)。
因此我们要求出每个字符做回文中心时的最长回文串,显然使用 Manacher。处理完成之后从后往前遍历每个字符,按如上流程处理即可。满足条件的记录并正序输出。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<bitset>
using namespace std;
const int N=1e6+10;
int T;
int len[2*N];
bitset<2*N>vis;
char c[N],s[2*N];
int main(){
scanf("%d",&T);
while(T--){
scanf("%s",c+1);
int n=strlen(c+1),tot=1;
s[0]='$',s[1]='#';
len[1]=vis[1]=vis[0]=len[0]=0;
for(int i=1;i<=n;i++){
s[++tot]=c[i];
s[++tot]='#';
len[tot]=len[tot-1]=0;
vis[tot]=vis[tot-1]=0;
}
s[tot+1]='^';
int rt=0,mx=0;
for(int i=1;i<=tot;i++){
if(i<=rt) len[i]=min(len[2*mx-i],rt-i+1);
while(s[i-len[i]]==s[i+len[i]]) len[i]++;
if(len[i]+i-1>rt){
mx=i;
rt=len[i]+i-1;
}
}
//因为 Manacher 的 len_i 记录的是加过隔板的串中包含回文中心的串半径,所以回文串末尾是 i+len_i-1,且该位一定为隔板,所以要获得末尾字符需 -2。同理,回文串开头是 i-(len_i-1)。
for(int i=tot;i>=1;i--){
if(s[i]=='#') continue;//回文中心为隔板的回文串,不能由翻转复制得到
if(i+len[i]-1==tot) vis[i]=1;
else if(vis[i+len[i]-2]&&i-(len[i]-1)==1) vis[i]=1;
}
for(int i=1;i<=tot;i++){
if(vis[i]) printf("%d ",i/2);
}
printf("\n");
}
return 0;
}
需要注意:因此题为多测,会出现上一组数据的串比下一组数据的串长的情况,因此每次计算完成后如果不清空 \(s\),上一组串的后半会接在下一组串的后面,在 Manacher 处理时可能会将回文串延伸到外面,因此要么每次清空 \(s\),要么在 \(s\) 串的末尾再添加一个哨兵字符,避免出现上述情况。

浙公网安备 33010602011771号