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;
}

感谢阅读 (求赞)~

posted @ 2022-03-25 07:25  pldzy  阅读(53)  评论(0)    收藏  举报