绿绿の串串题解

· · 题解

这道题其实只要学会马拉车算法( manacher )就 ok 了

如果你还没有了解马拉车算法这边建议你先去别的博客学习一下

luogu模板题

个人觉得很不错的一篇manacher算法详解博客

好,那么进入正题

本题要求一个串 R 使得它的前缀为 S ,串 R 可以进行任意次翻折

但是翻折点不会重叠

eg: R = "ab" 翻折一次后 R = "aba" 而非 R = "abba"

那么我们枚举每个点i的最大回文半径

情况一:

如果当前位置回文后正好到S串的末尾,则这是可行的

注:|S|为字符串S的长度

eg: S = "abcdcb", i = 4

r[4]$ = $2$ 且 $i$ + $r[4]$= $|S|

所以我们直接把 i 当做 R 翻折后的回文中心就可以了

对于例子来讲即:

$R_1$ = "abcdcba" 情况二: 如果 $i$ 位置回文后的位置可以为 $R$ 的回文中心且回文半径刚好延伸到 $S$ 的开头, 则可以在当前位置翻折到达$R$的回文中心再进行下一次翻折 eg:$S$ = "acbcac", $i$ = $3

显然当 i = 5时符合情况一

且 r[3] = 2, 3 + r[3] = 5

正好够到 S 的开头且够到 R 的可行回文中心

$R_1$ = "acbca" $R_2$ = "acbcacbca" CODE: ```cpp #include<iostream> #include<string> #include<cstring> using namespace std; const int N=2e7+10; int t; bool ans[N]; string S; char s[N]; int r[N]; int manacher(string str){ int len=str.size()*2+1; memset(ans,false,sizeof(ans)); memset(r,0,sizeof(r)); str=' '+str; int id=0,C=0,R=0,maxd=-1; for(int i=1;i<=len;++i)s[i]=(i&1)?'*':str[++id]; for(int i=1;i<=len;++i){ r[i]=i<R?min(R-i,r[2*C-i]):1; for(;i+r[i]<=len&&i-r[i]>=1;){ if(s[i+r[i]]==s[i-r[i]])++r[i]; else break; } if(i+r[i]>R){ R=i+r[i]; C=i; } maxd=max(maxd,r[i]); } return len; } int main(){ scanf("%d",&t); for(;t--;){ cin>>S; int len=manacher(S),lens=S.size(); for(int i=lens;i>=1;--i){ int ri=r[i*2]; if(ri>1){//如果原串的最大回文子串长度(不包括自己)>0 ri/=2,--ri;//计算这个回文中心在原串里的真正回文半径(不含本身) //如果把当前位置回文正好到S串的最左端,那么这个点就是R串的回文中心 if(i+ri==lens)ans[i]=true; //如果当前位置回文后的位置可以为R的回文中心且回文半径刚好延伸到S串的开头,则可以在当前位置翻折到达R点再进行下一次翻折 if(ans[i+ri]&&i-ri==1)ans[i]=true; } } for(int i=1;i<=lens;++i)if(ans[i])printf("%d ",i); puts(""); } return 0; } /* 4 abcdcb qwqwq qaqaqqq carnation */ ```