description

JOJO 的奇幻冒险是一部非常火的漫画。漫画中的男主角经常喜欢连续喊很多的「欧拉」或者「木大」。

为了防止字太多挡住漫画内容,现在打算在新的漫画中用 \(x\) 欧拉或者 \(x\) 木大表示有 \(x\) 个欧拉或者木大。

为了简化内容我们现在用字母表示喊出的话。

我们用数字和字母来表示一个串,例如:2 a 3 b 表示的串就是 aabbb

一开始漫画中什么话都没有,接下来你需要依次实现 \(n\) 个操作,总共只有 \(2\) 种操作:

  • 第一种:1 x c:在当前漫画中加入 \(x\) 个 \(c\),表示在当前串末尾加入 \(x\) 个 \(c\) 字符。保证当前串是空串或者串尾字符不是 \(c\);
  • 第二种:2 x:觉得漫画没画好,将漫画还原到第 \(x\) 次操作以后的样子,表示将串复原到第 \(x\) 次操作后的样子,如果 \(x = 0\) 则是将串变成空串。如果当前串是 bbaabbb,第 \(4\) 次操作后串是 bb,则 2 4 会使 bbaabbb 变成 bb,保证 \(x\) 小于当前操作数。

众所周知空条承太郎十分聪明,现在迪奥已经被打败了,他开始考虑自己的漫画中的一些问题:

对于一个串的每个前缀 \(A\),都有一个最长的比它短的前缀 \(B\) 与前缀 \(A\) 的一个后缀匹配,设这个最长的前缀 \(B\) 的长度为 \(L\)。\(L\) 为 \(0\) 时意味着 \(B\) 是一个空串。

每一次操作后,你都需要将当前的串的所有前缀的 \(L\) 求和并对 \(998244353\) 取模输出告诉空条承太郎,好和他的白金之星算出的答案对比。比如 bbaaabba 的 \(L\) 分别是 \(0,1,0,0,0,1,2,3\),所以对于这个串的答案就是 \(7\)。

solution

假如存在 s[1...x] = s[n-x+1...n],则中间完整的块必须字符与个数一一对应相等。

由此,我们不妨把某次操作增加的 (x, c) 视作二元组,对二元组做 kmp 求最大 border。

不过这里要特判一下第一个块:后半部分的第一个块长度 ≥ 前半部分第一个块(就是整个串第一个块)长度,且它们的字符相同,则认为它们相等。

可以发现这样操作依然满足 border 的传递性,实现时只在需要判相等时这样搞一下。

至于求答案。如果是整个串第一个块特判;否则边跳 fail 边记录当前已经匹配的最大值(需要注意即使存在长度 > 新加入的串长度也不能停止跳 fail,必须严格等于)。

还要特判上文所述 “后半部分的第一个块长度 ≥ 前半部分第一个块” 情况。

不过题目中还有 2 操作,离线下来发现就是 trie 上求最大 border。

然而 kmp 的复杂度是均摊的(虽然这道题你暴力跳好像也能过),我们考虑改一改求最大 border 的方法。

一种方法是建 O(∑)(字符集大小)个可持久化线段树,维护加入某一类字符共 k 个所转移到的点以及贡献。每次加入新点只会修改 O(1) 棵线段树。

另一种是利用 border 与循环的性质(形成 O(log) 个等差数列),每次如果当前循环节大小与上一次循环节大小相等,就可以直接对循环节取模。

accepted code

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std; const int MAXN = 100000;
const int MOD = 998244353; inline int add(int x, int y) {x += y; return x >= MOD ? x - MOD : x;}
inline int sub(int x, int y) {x -= y; return x < 0 ? x + MOD : x;}
inline int mul(int x, int y) {return (int) (1LL * x * y % MOD);} struct type{
int ch, cnt;
friend bool operator == (const type &a, const type &b) {
return (a.ch == b.ch) && (a.cnt == b.cnt);
}
}a[MAXN + 5]; vector<int>ch[MAXN + 5]; int ncnt;
int add(int x, type t) {
a[++ncnt] = t, ch[x].push_back(ncnt);
return ncnt;
} type stk[MAXN + 5]; int tp;
void update(int &res, int &lens, int k, int lim) {
if( lim > lens ) {
res = add(res, mul(sub(lim, lens), k));
res = add(res, (int)(1LL*(lens + 1 + lim)*(lim - lens)/2%MOD));
lens = lim;
}
}
int res[MAXN + 5], len[MAXN + 5], fail[MAXN + 5];
void insert(int x, type t) {
int nw = fail[tp], lens = 0, lst = -1;
stk[++tp] = t, len[tp] = len[tp - 1] + t.cnt, res[x] = 0;
while( nw != -1 ) {
if( stk[nw + 1].ch == t.ch ) {
update(res[x], lens, len[nw], min(stk[nw + 1].cnt, t.cnt));
if( nw == 0 && stk[1].cnt < t.cnt ) {
res[x] = add(res[x], mul(t.cnt - lens, stk[1].cnt));
break;
} else if( stk[nw + 1].cnt == t.cnt )
break;
}
if( nw && nw - fail[nw] == lst && 2*lst < nw )
nw %= lst;
else nw = fail[nw];
}
fail[tp] = nw + 1;
if( tp == 1 )
res[x] = add(res[x], (int)(1LL*(t.cnt - 1)*t.cnt/2%MOD));
} void dfs(int x, int ans) {
insert(x, a[x]), res[x] = add(res[x], ans);
for(unsigned i=0;i<ch[x].size();i++)
dfs(ch[x][i], res[x]);
tp--;
} int id[MAXN + 5];
int main() {
int n; scanf("%d", &n);
for(int i=1,op,x;i<=n;i++) {
scanf("%d%d", &op, &x);
if( op == 1 ) {
char str[2]; scanf("%s", str);
id[i] = add(id[i - 1], (type){str[0] - 'a', x});
} else id[i] = id[x];
}
fail[0] = -1;
for(unsigned i=0;i<ch[0].size();i++) dfs(ch[0][i], 0);
for(int i=1;i<=n;i++) printf("%d\n", res[id[i]]);
}

details

对于第二种方法,注意不能当循环节长度 < len/2 时就直接模循环节。

感性解释一下:因为我所要匹配的是 fail + 1 的位置,而循环节只能保证 [1...fail] 的有循环节。

【loj - 3055】「HNOI2019」JOJO的更多相关文章

  1. 【loj - 3056】 「HNOI2019」多边形

    目录 description solution accepted code details description 小 R 与小 W 在玩游戏. 他们有一个边数为 \(n\) 的凸多边形,其顶点沿逆时 ...

  2. 【LOJ 2004】「SDOI2017」硬币游戏

    LOJ 2004 100pts 首先我们肯定要建AC自动机的.. 那么这题就肯定是个AC自动机上\(dp\). 所以想想状态. 首先如果我们把状态设成这样行不行: \(dp(i)\)表示匹配到了i节点 ...

  3. 【LOJ 2145】「SHOI2017」分手是祝愿

    LOJ 2145 100pts 这题...BT啊 首先我们很容易想出\(dp(msk)\)表示现在灯开关的情况是\(msk\),期望通过多少步走到终结态. 很明显\(dp(msk)=\frac{1}{ ...

  4. 【LOJ 2144】「SHOI2017」摧毁「树状图」

    LOJ 2144 84pts 首先\(op2\)很简单.直接并查集一搞就好了(话说我现在什么东西都要写个并查集有点...) 然后\(op0\)我不会,就直接\(O(n^2)\)枚举一下\(P\)这个人 ...

  5. Loj #3055. 「HNOI2019」JOJO

    Loj #3055. 「HNOI2019」JOJO JOJO 的奇幻冒险是一部非常火的漫画.漫画中的男主角经常喜欢连续喊很多的「欧拉」或者「木大」. 为了防止字太多挡住漫画内容,现在打算在新的漫画中用 ...

  6. 【LOJ#6066】「2017 山东一轮集训 Day3」第二题(哈希,二分)

    [LOJ#6066]「2017 山东一轮集训 Day3」第二题(哈希,二分) 题面 LOJ 题解 要哈希是很显然的,那么就考虑哈希什么... 要找一个东西可以表示一棵树,所以我们找到了括号序列. 那么 ...

  7. 一本通1648【例 1】「NOIP2011」计算系数

    1648: [例 1]「NOIP2011」计算系数 时间限制: 1000 ms         内存限制: 524288 KB [题目描述] 给定一个多项式 (ax+by)k ,请求出多项式展开后 x ...

  8. 【LOJ 6041】「雅礼集训 2017 Day7」事情的相似度

    Description 人的一生不仅要靠自我奋斗,还要考虑到历史的行程. 历史的行程可以抽象成一个 01 串,作为一个年纪比较大的人,你希望从历史的行程中获得一些姿势. 你发现在历史的不同时刻,不断的 ...

  9. 【LOJ#536】「LibreOJ Round #6」花札

    题目链接 题目描述 「UniversalNO」的规则如下:每张牌有一种颜色和一个点数.两个人轮流出牌,由 Alice 先手,最开始牌堆为空,出的人可以出任意牌(放到牌堆顶),之后出的牌必须和当时牌堆顶 ...

随机推荐

  1. MY FIRST PAGE!

    RT. This is my first time to create and customize my cnblogs. Nice to see you!

  2. Spring 框架的 AOP 简介

    Spring 框架的 AOP Spring 框架的一个关键组件是面向方面的编程(AOP)(也称为面向切面编程)框架. 面向方面的编程需要把程序逻辑分解成不同的部分称为所谓的关注点. 跨一个应用程序的多 ...

  3. JVM_双亲委派机制

    双亲委派机制及作用 什么是双亲委派机制 当`.class`文件需要被加载时,它首先把这个任务委托给他的上级类加载器,层层往上委托,如果上级的类加载器没有加载过,自己才会去加载这个类. 源码分析 pro ...

  4. vscode环境配置(三)——解决控制台终端中文输出乱码

    由于系统终端默认编码为GBK,所以需要修改为UTF-8 方法一 打开cmd输入chcp查看编码格式,查看以及修改如下图所示: 方法二

  5. 设计模式系列之单例模式(Singleton Pattern)——确保对象的唯一性

    模式概述 模式定义 模式结构图 饿汉式单例与懒汉式单例 饿汉式单例 懒汉式单例 模式应用 模式在JDK中的应用 模式在开源项目中的应用 模式总结 主要优点 适用场景 说明:设计模式系列文章是读刘伟所著 ...

  6. 填坑!线上Presto查询Hudi表异常排查

    1. 引入 线上用户反馈使用Presto查询Hudi表出现错误,而将Hudi表的文件单独创建parquet类型表时查询无任何问题,关键报错信息如下 40931f6e-3422-4ffd-a692-6c ...

  7. thinkphp路由简介和设置使用

    use think\Route; //静态路由 Route::rule('/', 'index/index/index'); Route::rule('test', 'index/index/test ...

  8. 01 . Tomcat简介及部署

    Tomcat简介 Tomcat背景 tomcat就是常用的的中间件之一,tomcat本身是一个容器,专门用来运行java程序,java语言开发的网页.jsp就应该运行于tomcat中.而tomcat本 ...

  9. 搭建Prometheus平台,你必须考虑的6个因素

    作者简介 Loris Degioanni,Sysdig的创始人和CTO,同时还是容器安全工具Falco的创建者. 原文链接 https://thenewstack.io/6-things-to-con ...

  10. 【Hadoop】Hadoop的安装,本地模式、伪分布模式的配置

    Download hadoop-2.7.7.tar.gz 下载稳定版本的hadoop-2.7.7.tar.gz(我用的2.6.0,但是官网只能下载2.7.7的了) Required Software ...