P5446 [THUPC2018]绿绿和串串 题解
Neutralized
·
2022-02-05 15:18:34
·
题解
多半不会更好的阅读体验(雾
学校字符串考试的题,唯一一道 100\,pts 的,于是就来写TJ了(
1.题意
给定一个字符串 S ,判断 S 的前缀中有哪些长度满足将其以尾字符为轴翻转若干次后可以覆盖 S 并输出.
举个粒子,标红的字符为翻转轴:
对于串 S= \tt aabaaab 的前缀 \tt aab ,经过第一次翻转: \tt aa\color{red}b\color{black}aa ,不能覆盖 S ,再将这个串翻转,得到 \tt aaba\color{red}a\color{black}abaa ,这时发现它覆盖了 S ,所以长度 3 是合法的.
2.解析
看到翻转,马上想到这是考回文串的
看到回文串,马上想到了 \tt Manacher
但是,怎么利用 \tt Manacher ?
不难发现,以某个字符为轴翻转后,就形成了一个以它为轴的回文串,而以某个字符为轴的回文串的长度正好就是 \tt Manacher 要求的东西.
怎么判断它翻转过后是合法的呢?
考虑 \tt Manacher 求得回文串长时的结束条件: 指针指向的两个字符不相等,
也就是说,以某个位置为轴将左边的字符串(反向)和右边的字符串匹配时,在两个指针分别指向的位置失配了.
这个又有什么用呢?我们可以拿它来判断翻转过后是否合法!
合法的情况只有一种,就是这两个指针中的一个指向了空位置.而空位置只有两个: S 左边界的左边和 S 右边界的右边.
也就是说,只有回文左边界等于 S 左边界或者回文右边界等于 S 右边界的位置可能构成答案.
然后,我们要做的就是判断这些位置是否可以构成答案.
我们这里先假定字符串下标从 1 开始.
对于前缀 S_{1,x} ,分以下两种情况:
1.将它进行 1 次翻转后就可以覆盖 S .
这种情况下,它在 \tt Manacher 中跑出的回文串右边界一定等于 S 的右边界.因为这说明,在它的回文左边界更左边的字符,对称过后一定在 S 整个串的右边,可以忽略;而其它的字符构成以 x 为轴的回文串,所以对它做 1 次翻转就可以覆盖 S .
2.将它进行 1 次翻转后不能覆盖 S .
这种情况很好办,如果回文左边界不是 S 左边界,那么它就一定不能满足条件,直接否决它.
如果回文左边界是 S 左边界,那么我们可以直接跳跃到它的回文右边界,然后再进行判断,直到满足情况 1 或是被否决.
整体做法就是上面这样.
(如果您看到这里已经有了思路,您可以跳过以下内容)
有些同志可能对回文左/右边界比较迷惑,因为我考场上思考的时候比较乱,所以选定的回文边界比较奇特
这里作一点解释:
那么,对于每个位置,我们就求得了以它为轴的回文串的左边界和右边界.
注意这里的左边界和右边界并不是我们的回文左/右边界!回文左/右边界是左(右)边界的右(左)边一个的位置,因为左右边界对应的字符一定是 $\tt \#$ (或者其它添加的无关字符),所以我们把它们往左右推进一位,得到对应着原串 $S$ 中字符的边界.
对应到代码中就是:
```cpp
x的回文左边界: x-Rel[x]+2
x的回文右边界: x+Rel[x]-2
```
那么上面的思路也就~~不难理解了~~
## 3.Code
其实是考场代码,删除了~~奇奇怪怪的~~注释,对代码作用写了一点注释,并且做了一点点可读化处理.
```cpp
#include <bits/stdc++.h>
#define ri register int
#define MAXN 3000001 //Manacher记得开两倍……
#define ll long long
using namespace std;
int T,lens;
string s;
int Rel[MAXN];
inline void Sync(){
ios::sync_with_stdio(false);
cin.tie(NULL),cout.tie(NULL);
}
inline void Manacher(){
lens=s.length();
string buf="%#";
for(ri i=0;i<lens;i++)
buf+=s[i],buf+="#";
lens=buf.length();
ri ind=0,Mxr=0;
for(ri i=2,j;i<lens;i+=2){
j=(ind<<1)-i;
Rel[i]=(i<Mxr?min(Rel[j],Mxr-i):1);
while(buf[i+Rel[i]]==buf[i-Rel[i]])
++Rel[i];
if(i+Rel[i]>Mxr)
Mxr=i+Rel[i],ind=i;
}
} //就是普通的Manacher,跳的时候跳2个就行
#define reset(x,i) memset(x,i,sizeof(x))
int main()
{
Sync(); //Sync With Me !
cin>>T;
while(T--){
reset(Rel,0); //清空Manacher数组
cin>>s;
Manacher(); //跑Manacher
for(ri i=2;i<lens-2;i+=2){ //lens-2是最后一个非'#'字符
register bool f=0;
for(ri j=i;Rel[j]>2&&j+Rel[j]<=lens;j+=Rel[j]-2){ //Rel[j]>2限制了回文串长度必须大于1
if(j+Rel[j]==lens){ //对应情况1:回文右边界和串右边界相等
f=1; //标记答案
break; //直接退出去输出
}
if(j-Rel[j]>2&&j+Rel[j]<lens) //如果对称后不匹配
break; //直接退出
}
if(f) //有答案,输出
cout<<i/2<<" "; //Manacher串中下标为i,对应S中下标为i/2
} cout<<s.length()<<endl; //别忘了所有的S都可以作为初始串
}
return 0;
}
```
然后就做完辣!