P5446 [THUPC2018]绿绿和串串 题解
思路
1
题目给出的字符串可以是由一个子串 a 翻转而得;
而 a 也可以是由一个子串 b 翻转而得;
而 b 可以是由一个子串 c 翻转而得;
……
最终得到一个无法再翻转下去的子串 x。
而字符串本身、子串 a、子串 b、子串 c……子串 x 各自的长度就是我们要的答案。
例如:给出字符串 qwqwq ,
它可以是由子串 qwqw 翻转而得的;
而 qwqw 是由子串 qwq 翻转而得的;
而 qwq 是由子串 qw 翻转而得的;
然而, qw 无法由它的子串翻转而得。
即最终答案输出: 2 3 4 5 。
2
那么如何求每个字符串的最长翻转子串(此处不考虑字符串本身)呢?
拿例子来说:
已得 qwqwq 的最长翻转子串是 qwqw 。
qwqw 的翻转中心是最后一个的 w ,回看原串,可以发现一个由同一个 w 为翻转中心的回文串 qwq 。
再多举几个例子,就可以发现,一个字符串的最长翻转子串就是它自己去掉最短末尾回文串的一半。
还是举例子说明:
qwqw 的最短末尾回文串为 wqw ,
根据上文所说,我们去掉该回文串的后半部分 w ,
然后再拿剩下的和前面组合,就可以得到 qwqw 的最长翻转子串为:
q + wq = qwq 。
现在,我们的问题就转化为求出字符串中的每一个回文串了。
3
求回文串?就很简单能想到是 Manacher 算法了。
不会 Manacher 就可以先看看这道题:P3805 【模板】manacher 算法。
在 Manacher 求出了每一个回文串的基础上,我们从后面往前面找每个最长翻转子串。
具体代码作用见注释。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
#define re register
const int maxn = 1000005;
char st[maxn * 2];
int len[maxn * 2], cnt;
int vis[maxn * 2];
int T;
int mx, po;
inline void input ()
{
char ch = getchar ();
st[0] = '~', st[cnt = 1] = '#';
while (ch < 'a' or ch > 'z') ch = getchar ();
while (ch >= 'a' and ch <= 'z') st[++cnt] = ch, st[++cnt] = '#', ch = getchar ();
st[cnt + 1] = '%';
}
int main ()
{
scanf ("%d", &T);
while (T--)
{
cnt = mx = po = 0;
memset (vis, 0, sizeof vis);
memset (len, 0, sizeof len);
input ();
for (re int i (1); i <= cnt; ++i)//Manacher 模板
{
if (i <= mx) len[i] = min (mx - i, len[po * 2 - i]);
while (st[i + len[i]] == st[i - len[i]]) len[i]++;
if (i + len[i] > mx) mx = i + len[i] - 1, po = i;
}
for (re int i (cnt); i >= 1; --i)
{
if (i + len[i] - 1 == cnt) vis[i] = 1;//如果能一次翻转成
else if (vis[i + len[i] - 2] and i == len[i]) vis[i] = 1;//如果它能翻转成某个最长翻转子串,如 qwqwq 的 qwqw 的 qw ,且不会越界
}
for (re int i (1); i <= cnt; ++i) if (st[i] >= 'a' and st[i] <= 'z' and vis[i]) printf ("%d ", i / 2);
//Manacher 处理后的字符串转化成原字符串,长度直接除以 2 就可以得到原字符串的长度
printf ("\n");
}
return 0;
}
感谢阅读 (求赞)~

浙公网安备 33010602011771号