P5433 月宫的符卡序列

题意:

洛谷链接

T 组数据,(T5) ,每次给出一个字符串 S ,一个本质不同的回文串的价值是它在 S 中出现的所有位置的中点的异或和,假设这个回文串左右端点位置为 lr,那么中点位置为 l+r2S 标号从0开始)。输出价值最大的回文串的价值。(|S|1e6)

例如:串aabacabaaa,回文串"aba"价值为2^6=4,串"aa"价值为0^7^8=15。


(这曾经是 zzh 大佬一眼秒掉的题)

此题如果改为任意串而不是回文串,中点改为右端点,就可以用 sam 记录串出现的每一个位置并沿 fail 树合并,直接拓扑把价值向 fail 树上加。
中点改为左端点跑反串即可。

而现在要找回文串,我们只有俩工具:manacherPAM 。由于此题价值在中点处,我们尝试使用 manacher,然后会发现 PAM 不能解决这个问题,过会儿解释原因。

首先我们考虑到一个事实:长度为 n 的字符串(包括 manacher 中间添加的字符)最多有 n 个本质不同的回文串。因为在进行 manacher 算法时,右端每向后扩展一个字符,最多产生一个新的回文串,如果以前出现过就会小于 n

建一颗回文树:

然后我们发现有些不同的回文串中点的位置是相同的(也不是完全相同),较长的回文串的中点位置集合肯定是较短的回文串中点位置集合的子集。我们考虑将每个本质不同的回文串变成一个点,在中点相同的较长与较短的回文串之间连上父子关系形成一颗树。由于回文树是 PAM 里的称呼,我也不知道这棵树该叫啥,但其实长的差不多,就也叫回文树了。

这颗回文树和 PAM 原理一样,一个点到根的所有字符表示的是从中间到两端的字符,不过由于 manacher 添加了中间字符,这棵树没有分奇数和偶数根,也不用记录 fail

建立过程就是在 manacher 算法进行的同时,如果向右扩展找到了新的字符,就新建一个点挂在它的父亲节点上。每个位置找到最长的回文串后,该节点到根的所有节点都要加上这个中点的价值,树剖显然不吃香,既然我们只需看最后的价值那么直接拓扑就好惹。

那么既然这颗树和 PAM 这么像为什么不能用 PAM 做呢?

因为 PAM 建出来的 fail 树是按右端点相同的回文串连接起来的,不知道每个串中点具体位置很难加上贡献。那么考虑如果用 PAM 中的实边呢,它确实是由同中点的串连起来的,但是 PAM 的枚举顺序不同,毕竟 PAM 是由右端点构建出来的。如果将整条链价值全部加上那么祖先节点一定会被多算,如果只将当前节点价值加上那么 fail 链上的那些串价值是不会再被加上的。

不过如果有一题让你求每个本质不同的字符串出现的次数或左右端点位置和,可以用 PAMfail 树上拓扑。

关于题解:

manacher 建树的过程中需要判断一个串是否为新串或者其为哪个点,我们可以用 hash。有一种黑科技叫 pbds 听说可以 O(N) 但是我不会用,或者别的方法好像也可以,但博主太菜所以用了 map。这题洛谷上得开 O2 才能过,所以也算不上正解,意会一下即可。

最后把垃圾代码留下:

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

__EOF__

本文作者枫叶晴
本文链接https://www.cnblogs.com/maple276/p/12790705.html
关于博主:菜菜菜
版权声明:呃呃呃
声援博主:呐呐呐
posted @   maple276  阅读(220)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示