【UOJ 429】串串划分(Runs)(容斥)+ 有关 Lyndon Tree 及其应用的小小记录

串串划分

题目链接:UOJ 429

题目大意

给你一个字符串,然后问你有多少个划分,满足相邻的两个串不相等,且每个串都不存在整周期。

思路

Lyndon Tree

Lyndon Tree 是什么呢?
它就是你不是可以通过把两个串 Ly 拼起来得到新的 Ly 吗,那我们对于一个串找它最小的后缀(不能算它自己),然后得到两个串,然后一直划分下去,就得到了 Lyndon Tree

然后这个东西是建立在这个串是 Ly 的基础上的,如果不是的话就是对于 Ly 划分的每个串都这么弄,形成一个森林。

然后你思考一下会发现它其实是 SArank 数组建笛卡尔树得到的树。

然后有一些性质:
如果 [l,r]Ly,那 l,r 字符对应点在树上的 LCAa(设),对于子串 [la,ra],那会有 l=larar
然后如果这个区间是 Lys,f(l)(不难感受到这个树也是分 f 的),那一定是在右儿子(因为是最长的 Ly 啊)
然后差不多的道理,任意一个 runLyR 一定会在右儿子出现(这个就在 f{0,1} 的其中一个),然后好像是看那个满足 sr+1<fsr+1p


似乎之前没有说过 WPLPL,这里提一句把。

对于一个串 Sp,q 两个周期,如果 p+q|S|,那么还有 gcd(p,q) 的周期(WPLWeak Periodicity Lemma
(这个你把一个位置用两个周期移一下可以得到减的位置,然后辗转相除)

然后其实可以扩展到 p+qgcd(p,q)|S|,就是 PLPeriodicityLemma 了。
(不会证,好像可以先举例得到是上界,然后比例巴拉搞一通)


有什么用呢,可以搞一个叫做“子串半周期查询”的东西(Two-Period Queries)

这个东西是要你快速查询一个子串是否有不超过长度一半的周期,如果有输出最小的。

我们定义一个 exrun(i,j) 为满足 ii,jj,p(ji+1)/2 的一个 run(i,j,p)
然后我们可以证出它如果存在就是唯一。
(因为如果不是唯一,重叠的那些可以用 WPL 发现矛盾)

那这个定义就可以解决上面的问题,我们考虑对 f{0,1} 都建出 Lyndon Tree,然后让 a=lca([i,(i+j)/2]),然后判断右儿子是不是满足条件就可以了。

至于有这个位置可以判就是因为一定有对应的 LyR 包含 (i+j)/2,那根据上面的性质,就会在某个点的右儿子位置出现。
然后我们会发现 a 的位置也会 >p,也包含那个位置,所以 a 会是这个 LyR 的祖先。
那只要它右儿子不是那个 LyR,那也会是它祖先。

那都是右儿子可以得到位置依次变大。
如果中间那个的 j,那它对应的位置有 p 这个周期,那它就不是 Ly 了,矛盾。
如果 >j,那个 LyR 的左边界跟它的右边界形成的区间会小于它形成的区间,那也跟它是 Ly 矛盾了(定义上)。
所以就反证了。

这道题

这道题还是很友好滴!
(至少不用树状数组)

考虑找不合法的是怎样,看到划分考虑给每一种划分出的字符串一个函数,然后一个划分的权值就是所有的乘起来(这样出现不合法的就直接给 0
然后发现如果只是要没有整周期就可以弄,但是有说两个之间不能是相同的。

考虑到这个相同的很相似,你如果把两个相同的并起来就形成了前面的的整周期。
所以你考虑容斥掉,就整周期的情况跟两个之间的抵消掉。
然后思考一下不难发现容斥系数就是这个串它最小循环节的出现次数。

那考虑 DP,不难得到 n2 的形式(枚举新的子串是从哪里开始),考虑优化。
那首先特别的地方是有整周期的,那别的我们就可以直接前缀和,然后减去有整周期的。

然后考虑整周期,首先把 run 都找出来,那我们就可以得到一些区间。
发现对于一个 runr 它能提供给一个点能从转移的位置是一个等差序列。
那我们只需要对于每个 r 维护当前出现的每个等差序列它们出现的点的贡献和即可。
然后注意到出现一个点,那别的之前的点的循环节出现次数 +1(亦或者说全部加一,最后这个从 01),然后你取反一下然后加即可。

代码

#include<cstdio> #include<vector> #include<cstring> #include<algorithm> #define ll long long #define mo 998244353 #define ull unsigned long long using namespace std; const int N = 2e5 + 100; char s[N]; int n, ly[N], m; struct RUNS { int x, y, p; }runs[N << 1]; vector <int> ed[N]; vector <ll> sum[N << 1][2]; //0:减去前缀和 //1:容斥 ll f[N]; struct HASH { const int di = 131; ull mi[N], hash[N]; void Init() { mi[0] = 1; for (int i = 1; i <= n; i++) { mi[i] = mi[i - 1] * di; hash[i] = hash[i - 1] * di + s[i] - 'a' + 1; } } ull get(int x, int y) { return hash[y] - hash[x - 1] * mi[y - x + 1]; } }H; int getl(int x, int y) { int l = 1, r = min(x, y), re = 0; while (l <= r) { int mid = (l + r) >> 1; if (H.get(x - mid + 1, x) == H.get(y - mid + 1, y)) re = mid, l = mid + 1; else r = mid - 1; } return re; } int getr(int x, int y) { int l = 1, r = min(n - x + 1, n - y + 1), re = 0; while (l <= r) { int mid = (l + r) >> 1; if (H.get(x, x + mid - 1) == H.get(y, y + mid - 1)) re = mid, l = mid + 1; else r = mid - 1; } return re; } int sta[N]; bool cmpS(int x, int y) { int pl = getr(x, y); return s[x + pl] < s[y + pl]; } void Lyndon(bool op) { sta[0] = 0; sta[++sta[0]] = n + 1; sta[++sta[0]] = n; ly[n] = n; for (int i = n - 1; i >= 1; i--) { while (sta[0] > 1 && cmpS(i, sta[sta[0]]) == op) sta[0]--; sta[++sta[0]] = i; ly[i] = sta[sta[0] - 1] - 1; } } void check(int x, int y) { int l = getl(x, y), r = getr(x, y); if (l + r >= y - x + 1) runs[++m] = (RUNS){x - l + 1, y + r - 1, y - x}; } bool cmp_runs(RUNS x, RUNS y) { if (x.x != y.x) return x.x < y.x; if (x.y != y.y) return x.y < y.y; return x.p < y.p; } void Init() { H.Init(); for (int op = 0; op <= 1; op++) { Lyndon(op); for (int i = 1; i <= n; i++) { check(i, ly[i] + 1); } } sort(runs + 1, runs + m + 1, cmp_runs); int tmp = m; m = 0; for (int i = 1; i <= tmp; i++) if (runs[i].x != runs[i - 1].x || runs[i].y != runs[i - 1].y) runs[++m] = runs[i]; } int main() { scanf("%s", s + 1); n = strlen(s + 1); Init(); for (int i = 1; i <= m; i++) { for (int r = runs[i].x + 2 * runs[i].p - 1; r <= runs[i].y; r++) ed[r].push_back(i); int sz = runs[i].p; if (runs[i].y - (runs[i].x + 2 * runs[i].p - 1) + 1 <= runs[i].p) sz = (runs[i].y - runs[i].x + 1) % runs[i].p; sz++; sum[i][0].resize(sz); sum[i][1].resize(sz); } f[0] = 1; ll Sum = 1; for (int i = 1; i <= n; i++) { f[i] = Sum; for (int j = 0; j < ed[i].size(); j++) { int v = ed[i][j]; int l = runs[v].x, r = runs[v].y, p = runs[v].p; int id = (i - l + 1) % p; (sum[v][0][id] += mo - f[i - 2 * p]) %= mo; (sum[v][1][id] *= mo - 1) %= mo; (sum[v][1][id] += mo - f[i - 2 * p]) %= mo; (f[i] += sum[v][0][id] + sum[v][1][id]) %= mo; } (Sum += f[i]) %= mo; } printf("%lld", f[n]); return 0; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/UOJ_429.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(56)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示