当前位置:网站首页>【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;
}
边栏推荐
- 规则引擎Drools的使用
- Method of test case design -- move combination, causal judgment
- Makefile knowledge rearrangement (super detailed)
- Solution: runtimeerror: expected object of scalar type int but got scalar type double
- UE4 多个角色控制权的切换
- Acwing_12. 背包问题求具体方案_dp
- 生活相关——十年的职业历程(转)
- Firewall command simple operation
- What are the duplicate check rules for English papers?
- Functions of anonymous functions
猜你喜欢

使用百度飞桨EasyDL完成垃圾分类

Makefile knowledge rearrangement (super detailed)

软模拟光栅化渲染器

I.MX6U-ALPHA开发板(主频和时钟配置实验)

机器学习之桑基图(用于用户行为分析)

How does win11 set the theme color of the status bar? Win11 method of setting theme color of status bar

HelloWorld case analysis

Acwing_ 12. Find a specific solution for the knapsack problem_ dp

The era of smart clothes has come. Zhinar invites you to discuss swords in Yangcheng. Guangya Exhibition is waiting for you on August 4

理性认知教育机器人寓教于乐的辅助作用
随机推荐
Unable to find sygwin.s file during vscode debugging
MySQL - multi table query - Cartesian product sum, correct multi table query, equivalent connection and unequal connection, inner connection and outer connection
I.MX6U-ALPHA开发板(GPIO中断实验)
1. Excel的IF函数
MySQL usage
第三篇如何使用SourceTree提交代码
华为再发“天才少年”全球召集令,有人曾放弃360万年薪加入
青少年创客教育的创意设计原理
Sorting and searching
Recommendation | DBT skills training manual: baby, you are the reason why you live
TIA botu WinCC Pro controls the display and hiding of layers through scripts
makefile知识再整理(超详细)
生活相关——减少期待,更快乐
再获认可 | 赛宁网安连续上榜《CCSIP 2022中国网络安全产业全景图》
Day26 job
Postman imports curl, exports curl, and exports corresponding language codes
AWS Support Plan
egg-sequelize JS编写
[C language foundation] 13 preprocessor
荐书 |《学者的术与道》:写论文是门手艺