当前位置:网站首页>【UOJ 429】串串划分(Runs)(容斥)+ 有关 Lyndon Tree 及其应用的小小记录
【UOJ 429】串串划分(Runs)(容斥)+ 有关 Lyndon Tree 及其应用的小小记录
2022-07-26 04:24:00 【SSL_TJH】
串串划分
题目链接:UOJ 429
题目大意
给你一个字符串,然后问你有多少个划分,满足相邻的两个串不相等,且每个串都不存在整周期。
思路
Lyndon Tree
L y n d o n T r e e \rm Lyndon\ Tree Lyndon Tree 是什么呢?
它就是你不是可以通过把两个串 L y \rm Ly Ly 拼起来得到新的 L y \rm Ly Ly 吗,那我们对于一个串找它最小的后缀(不能算它自己),然后得到两个串,然后一直划分下去,就得到了 L y n d o n T r e e \rm Lyndon\ Tree Lyndon Tree。
然后这个东西是建立在这个串是 L y \rm Ly Ly 的基础上的,如果不是的话就是对于 L y \rm Ly Ly 划分的每个串都这么弄,形成一个森林。
然后你思考一下会发现它其实是 S A \rm SA SA 的 r a n k \rm rank rank 数组建笛卡尔树得到的树。
然后有一些性质:
如果 [ l , r ] [l,r] [l,r] 是 L y \rm Ly Ly,那 l , r l,r l,r 字符对应点在树上的 L C A \rm LCA LCA 是 a a a(设),对于子串 [ l a , r a ] [l_a,r_a] [la,ra],那会有 l = l a ⩽ r a ⩽ r l=l_a\leqslant r_a\leqslant r l=la⩽ra⩽r。
然后如果这个区间是 L y s , f ( l ) \rm {Ly}_{s,f}(l) Lys,f(l)(不难感受到这个树也是分 f f f 的),那一定是在右儿子(因为是最长的 L y \rm Ly Ly 啊)
然后差不多的道理,任意一个 r u n \rm run run 的 L y R \rm LyR LyR 一定会在右儿子出现(这个就在 f ∈ { 0 , 1 } f\in\{0,1\} f∈{ 0,1} 的其中一个),然后好像是看那个满足 s r + 1 < f s r + 1 − p s_{r+1}<_fs_{r+1-p} sr+1<fsr+1−p
似乎之前没有说过 W P L \rm WPL WPL 和 P L \rm PL PL,这里提一句把。
对于一个串 S S S 有 p , q p,q p,q 两个周期,如果 p + q ⩽ ∣ S ∣ p+q\leqslant |S| p+q⩽∣S∣,那么还有 gcd ( p , q ) \gcd(p,q) gcd(p,q) 的周期( W P L \rm WPL WPL, W e a k P e r i o d i c i t y L e m m a \rm Weak\ Periodicity\ Lemma Weak Periodicity Lemma)
(这个你把一个位置用两个周期移一下可以得到减的位置,然后辗转相除)
然后其实可以扩展到 p + q − gcd ( p , q ) ⩽ ∣ S ∣ p+q-\gcd(p,q)\leqslant |S| p+q−gcd(p,q)⩽∣S∣,就是 P L \rm PL PL, P e r i o d i c i t y L e m m a \rm Periodicity Lemma PeriodicityLemma 了。
(不会证,好像可以先举例得到是上界,然后比例巴拉搞一通)
有什么用呢,可以搞一个叫做“子串半周期查询”的东西(Two-Period Queries)
这个东西是要你快速查询一个子串是否有不超过长度一半的周期,如果有输出最小的。
我们定义一个 e x r u n ( i , j ) {\rm exrun}(i,j) exrun(i,j) 为满足 i ′ ⩽ i , j ⩽ j ′ , p ⩽ ( j − i + 1 ) / 2 i'\leqslant i,j\leqslant j',p\leqslant(j-i+1)/2 i′⩽i,j⩽j′,p⩽(j−i+1)/2 的一个 r u n ( i ′ , j ′ , p ) \rm run(i',j',p) run(i′,j′,p)。
然后我们可以证出它如果存在就是唯一。
(因为如果不是唯一,重叠的那些可以用 W P L \rm WPL WPL 发现矛盾)
那这个定义就可以解决上面的问题,我们考虑对 f ∈ { 0 , 1 } f\in\{0,1\} f∈{ 0,1} 都建出 L y n d o n T r e e \rm{Lyndon\ Tree} Lyndon Tree,然后让 a = l c a ( [ i , ⌈ ( i + j ) / 2 ⌉ ] ) a={\rm lca}([i,\left\lceil(i+j)/2\right\rceil]) a=lca([i,⌈(i+j)/2⌉]),然后判断右儿子是不是满足条件就可以了。
至于有这个位置可以判就是因为一定有对应的 L y R \rm LyR LyR 包含 ⌈ ( i + j ) / 2 ⌉ \left\lceil(i+j)/2\right\rceil ⌈(i+j)/2⌉,那根据上面的性质,就会在某个点的右儿子位置出现。
然后我们会发现 a a a 的位置也会 > p >p >p,也包含那个位置,所以 a a a 会是这个 L y R \rm LyR LyR 的祖先。
那只要它右儿子不是那个 L y R \rm LyR LyR,那也会是它祖先。
那都是右儿子可以得到位置依次变大。
如果中间那个的 ⩽ j \leqslant j ⩽j,那它对应的位置有 p p p 这个周期,那它就不是 L y \rm Ly Ly 了,矛盾。
如果 > j >j >j,那个 L y R \rm LyR LyR 的左边界跟它的右边界形成的区间会小于它形成的区间,那也跟它是 L y \rm Ly Ly 矛盾了(定义上)。
所以就反证了。
这道题
这道题还是很友好滴!
(至少不用树状数组)
考虑找不合法的是怎样,看到划分考虑给每一种划分出的字符串一个函数,然后一个划分的权值就是所有的乘起来(这样出现不合法的就直接给 0 0 0)
然后发现如果只是要没有整周期就可以弄,但是有说两个之间不能是相同的。
考虑到这个相同的很相似,你如果把两个相同的并起来就形成了前面的的整周期。
所以你考虑容斥掉,就整周期的情况跟两个之间的抵消掉。
然后思考一下不难发现容斥系数就是这个串它最小循环节的出现次数。
那考虑 DP,不难得到 n 2 n^2 n2 的形式(枚举新的子串是从哪里开始),考虑优化。
那首先特别的地方是有整周期的,那别的我们就可以直接前缀和,然后减去有整周期的。
然后考虑整周期,首先把 r u n \rm run run 都找出来,那我们就可以得到一些区间。
发现对于一个 r u n \rm run run, r r r 它能提供给一个点能从转移的位置是一个等差序列。
那我们只需要对于每个 r r r 维护当前出现的每个等差序列它们出现的点的贡献和即可。
然后注意到出现一个点,那别的之前的点的循环节出现次数 + 1 +1 +1(亦或者说全部加一,最后这个从 0 0 0 到 1 1 1),然后你取反一下然后加即可。
代码
#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;
}
边栏推荐
- Keil V5 installation and use
- Method of test case design: introduction, trial recruit, preliminary exploration of equivalence boundary
- What model is good for the analysis of influencing factors?
- Getting started with mongodb Basics
- [project chapter - how to write and map the business model? (3000 word graphic summary suggestions)] project plan of innovation and entrepreneurship competition and application form of national Entrep
- APISIX 在 API 和微服务领域的探索
- 红星美凯龙高负债之下,盯上新能源了?
- Life related -- the heartfelt words of a graduate tutor of Huake (mainly applicable to science and Engineering)
- 使用百度飞桨EasyDL完成垃圾分类
- 吴恩达机器学习课后习题——线性回归
猜你喜欢

UE4 获取玩家控制权的两种方式

低成本、快速、高效搭建数字藏品APP、H5系统,扇贝科技专业开发更放心!

Makefile knowledge rearrangement (super detailed)

HelloWorld case analysis

TIA botu WinCC Pro controls the display and hiding of layers through scripts

Support proxy direct connection to Oracle database, jumpserver fortress v2.24.0 release

SEGGER Embedded Studio找不到xxx.c或者xxx.h文件

Retail chain store cashier system source code management commodity classification function logic sharing

Scroll view pull-down refresh and pull-up load (bottom)

解析Steam教育的课程设计测评体系
随机推荐
Acwing刷题
雅迪高端之后开始变慢
Weights & Biases (二)
P-norm (2-norm is Euclidean norm)
MySQL日志分类:错误日志、二进制日志、查询日志、慢查询日志
Retail chain store cashier system source code management commodity classification function logic sharing
Life related -- the heartfelt words of a graduate tutor of Huake (mainly applicable to science and Engineering)
综合评价与决策方法
机器学习之信用卡欺诈检测
Tutorial on using the one click upgrade function of the rtsp/onvif protocol video platform easynvr service
Steam科学教育赋予课堂教学的创造力
Life related - less expectation, happier
低成本、快速、高效搭建数字藏品APP、H5系统,扇贝科技专业开发更放心!
Dynamic planning for stair climbing
makefile知识再整理(超详细)
Scroll view pull-down refresh and pull-up load (bottom)
数据仓库
Functions of anonymous functions
第三篇如何使用SourceTree提交代码
Support proxy direct connection to Oracle database, jumpserver fortress v2.24.0 release