P5433 月宫的符卡序列
题意:
\(T\) 组数据,\((T\leq5)\) ,每次给出一个字符串 \(S\) ,一个本质不同的回文串的价值是它在 \(S\) 中出现的所有位置的中点的异或和,假设这个回文串左右端点位置为 \(l\) 和 \(r\),那么中点位置为 \(\left\lfloor\frac {l+r}2\right\rfloor\)(\(S\) 标号从0开始)。输出价值最大的回文串的价值。\((\vert S\vert\leq1e6)\)
例如:串aabacabaaa,回文串"aba"价值为2^6=4,串"aa"价值为0^7^8=15。
(这曾经是 \(zzh\) 大佬一眼秒掉的题)
此题如果改为任意串而不是回文串,中点改为右端点,就可以用 \(sam\) 记录串出现的每一个位置并沿 \(fail\) 树合并,直接拓扑把价值向 \(fail\) 树上加。
中点改为左端点跑反串即可。
而现在要找回文串,我们只有俩工具:\(manacher\) 和 \(PAM\) 。由于此题价值在中点处,我们尝试使用 \(manacher\),然后会发现 \(PAM\) 不能解决这个问题,过会儿解释原因。
首先我们考虑到一个事实:长度为 \(n\) 的字符串(包括 \(manacher\) 中间添加的字符)最多有 \(n\) 个本质不同的回文串。因为在进行 \(manacher\) 算法时,右端每向后扩展一个字符,最多产生一个新的回文串,如果以前出现过就会小于 \(n\)。
建一颗回文树:
然后我们发现有些不同的回文串中点的位置是相同的(也不是完全相同),较长的回文串的中点位置集合肯定是较短的回文串中点位置集合的子集。我们考虑将每个本质不同的回文串变成一个点,在中点相同的较长与较短的回文串之间连上父子关系形成一颗树。由于回文树是 \(PAM\) 里的称呼,我也不知道这棵树该叫啥,但其实长的差不多,就也叫回文树了。
这颗回文树和 \(PAM\) 原理一样,一个点到根的所有字符表示的是从中间到两端的字符,不过由于 \(manacher\) 添加了中间字符,这棵树没有分奇数和偶数根,也不用记录 \(fail\)。
建立过程就是在 \(manacher\) 算法进行的同时,如果向右扩展找到了新的字符,就新建一个点挂在它的父亲节点上。每个位置找到最长的回文串后,该节点到根的所有节点都要加上这个中点的价值,树剖显然不吃香,既然我们只需看最后的价值那么直接拓扑就好惹。
那么既然这颗树和 \(PAM\) 这么像为什么不能用 \(PAM\) 做呢?
因为 \(PAM\) 建出来的 \(fail\) 树是按右端点相同的回文串连接起来的,不知道每个串中点具体位置很难加上贡献。那么考虑如果用 \(PAM\) 中的实边呢,它确实是由同中点的串连起来的,但是 \(PAM\) 的枚举顺序不同,毕竟 \(PAM\) 是由右端点构建出来的。如果将整条链价值全部加上那么祖先节点一定会被多算,如果只将当前节点价值加上那么 \(fail\) 链上的那些串价值是不会再被加上的。
不过如果有一题让你求每个本质不同的字符串出现的次数或左右端点位置和,可以用 \(PAM\) 在 \(fail\) 树上拓扑。
关于题解:
跑 \(manacher\) 建树的过程中需要判断一个串是否为新串或者其为哪个点,我们可以用 \(hash\)。有一种黑科技叫 \(pbds\) 听说可以 \(O(N)\) 但是我不会用,或者别的方法好像也可以,但博主太菜所以用了 \(map\)。这题洛谷上得开 \(O_2\) 才能过,所以也算不上正解,意会一下即可。
最后把垃圾代码留下:
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#define QWQ cout<<"QwQ"<<endl;
#define ll long long
#include <vector>
#include <queue>
#include <stack>
#include <map>
#define RR register
using namespace std;
const int N=1010101;
const int mp=998244353;
int T;
int n,m;
ll ha[N<<1],g[N<<1];
char s[N<<1],z[N];
char fen = '{';
map <int,int> f;
int st[N<<1],fa[N<<1];
int p[N<<1];
int ans,id,R;
int val[N<<1],tot;
void chushihua() {
ans = R = id = 0;
memset(val,0,sizeof(val));
memset(p,0,sizeof(p));
while(tot) f[st[tot--]] = 0; tot = 1;
}
void SAKI() {
int H;
for(RR int i=1;i<=n;i++) {
int now = 1, xin = 0;
if(i<=R) p[i] = min(R-i+1,p[id*2-i]);
else p[i] = 1;
H = (ha[i+p[i]-1] - ha[i-1] * g[i+p[i]-i] % mp + mp) %mp;
int &uuz = f[H]; if(!uuz) { st[++tot] = H; fa[tot] = now; uuz = tot; }
now = uuz;
while(s[i+p[i]]==s[i-p[i]]) {
p[i]++;
H = (ha[i+p[i]-1] - ha[i-1] * g[i+p[i]-i] % mp + mp) %mp;
int &uz = f[H]; if(!uz) { st[++tot] = H; fa[tot] = now; uz = tot; }
now = uz;
}
if(i+p[i]-1 > R) { R = i+p[i]-1; id = i; }
val[now] ^= (i>>1)-1;
}
}
int main() {
scanf("%d",&T);
g[0] = 1;
for(RR int i=1;i<=(N<<1)-1000;i++) g[i] = g[i-1] * 29 % mp;
while(T--) {
chushihua();
scanf("%s",z+1); m = strlen(z+1);
for(RR int i=1;i<=m;i++) s[i<<1] = z[i], s[(i<<1)-1] = fen;
n = m<<1|1; s[n] = fen, s[n+1] = '@';
for(RR int i=1;i<=n;i++) ha[i] = (ha[i-1] * 29 + s[i] - 'a' + 1) % mp;
SAKI();
for(RR int i=tot;i>=3;i--) { val[fa[i]] ^= val[i]; ans = max(ans,val[i]); }
cout<<ans<<"\n";
}
return 0;
}