【HNOI 2019】JOJO
Problem
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\)
Input Format
第一行包括一个正整数 \(n\),表示操作数量。
接下来 \(n\) 行每行包含一个操作,操作格式如题目描述所示,例如:
1 x c2 x
保证数据合法。
Output Format
仅包含 \(n\) 行,第 \(i\) 行一个整数,表示 \(i\) 个操作之后串的答案。
Sample
Input
11
1 2 a
1 3 b
1 2 a
1 1 b
2 2
1 3 a
1 2 b
2 6
2 5
1 7 a
1 5 c
Output
1
1
4
7
1
6
13
6
1
14
14
Explanation
Explanation for Sample
| 操作 | 此时的串 | 答案(取模后) |
|---|---|---|
| \(1\) | aa |
\(0+1=1\) |
| \(2\) | aabbb |
\(0+1+0+0+0=1\) |
| \(3\) | aabbbaa |
\(0+1+0+0+0+1+2=4\) |
| \(4\) | aabbbaab |
\(0+1+0+0+0+1+2+3=7\) |
| \(5\) | aabbb |
\(0+1+0+0+0=1\) |
| \(6\) | aabbbaaa |
\(0+1+0+0+0+1+2+2=6\) |
| \(7\) | aabbbaaabb |
\(0+1+0+0+0+1+2+2+3+4=13\) |
| \(8\) | aabbbaaa |
\(0+1+0+0+0+1+2+2=6\) |
| \(9\) | aabbb |
\(0+1+0+0+0=1\) |
| \(10\) | aabbbaaaaaaa |
\(0+1+0+0+0+1+2+2+2+2+2+2=14\) |
| \(11\) | aabbbaaaaaaaccccc |
\(0+1+0+0+0+1+2+2+2+2+2+2+0+0+0+0+0=14\) |
Range
\(20\%\) 的数据满足 \(n\le 300\),对于每个 \(1\) 操作中的 \(x\le 300\);
另有 \(30\%\) 的数据满足 \(n\le 10^5\),且对于每个 \(1\) 操作中的 \(x=1\);
另有 \(30\%\) 的数据满足 \(n\le 10^5\),且不含 \(2\) 操作;
\(100\%\) 的数据满足 \(n\le 10^5\),且每个 \(1\) 操作中的 \(x\le 10^4\)。
Algorithm
\(KMP\) 。
Mentality
挺神的一道题。
对于每次第一种操作加入的字符,我们将其看作一个整体,可以称其为字段,一个字段拥有字符与长度两种属性。
先考虑一个 \(50\) 分做法(虽然说是 \(50\) 分,但本题数据水,实际上可以 \(A\) 掉):当我们在结尾加入一个字符时,回想一下跳 \(KMP\) 的过程:不断跳前一位的 \(nx\) ,直到当前位置的后一个字符与加入字符相同。

那么由于每次加入的字段都与前面的字符不同,则我们发现,对于一对相同的前后缀,删掉开头结尾的第一个字段,中间的都是完整的字段。那么我们可以将一个字段视作一个新的字符进行 \(KMP\) ,同时特别的,对于第一个字段,我们将所有与它字符相同且长度大于它的字段视作相同字段。
那么每次新加入一个字段,我们只需要不断跳 \(nx\) 并计算答案。
虽然此算法能通过此题,但毕竟复杂度不正确,因为 \(KMP\) 跳数组的 \(O(n)\) 是均摊意义下的,若有回溯操作并刻意构造就能够完美卡掉它。那么考虑令跳 \(KMP\) 的过程复杂度正确。
可以考虑一个平时由于复杂度均摊而完全不会考虑的优化:循环节。对于跳 \(nx\) ,假设当前在位置 \(i\) ,若 \(nx_i < \frac{i}{2}\) ,则跳 \(nx\) 会使长度减少到一半以下。但如果 \(nx_i > \frac{i}{2}\) ,则可能导致长度只会减少一点点,从而复杂度错误。
但是,如果 \(nx_i > \frac{i}{2}\) ,它就会产生至少两个循环节!(譬如 \(ABABA\) 的形式)那么我们只需要加上一个判断:若当前前缀 \(i\) 存在循环节,先判断末尾循环节是否满足要求,然后调试第一个循环节即可。
这样的话每次长度必定缩短一半以上,则跳 \(KMP\) 的复杂度上限优化为每次 \(O(log(n))\) ,总复杂度 \(O(nlogn)\)。
Code
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <vector>
using namespace std;
const int Max_n = 1e5 + 5, mod = 998244353;
int n;
long long Ans[Max_n];
int f[Max_n], l[Max_n], len[Max_n], sum[Max_n];
char P[Max_n], c[Max_n];
int cntr, hd[Max_n], nx[Max_n], to[Max_n];
void addr(int u, int v) {
cntr++;
nx[cntr] = hd[u], to[cntr] = v;
hd[u] = cntr;
}
void Mod(long long &x) { x %= mod; }
void calc(int x, int L, long long ans) {
if (len[x]) {
if (!L) Mod(ans = (len[x] - 1) * len[x] / 2);
int maxx = 0, now = f[L], lastgap = 0;
for (int i = f[L]; ~i; i = f[i]) {
if (P[i + 1] == c[x] && min(l[i + 1], len[x]) > maxx) {
int tp = maxx;
maxx = min(l[i + 1], len[x]);
Mod(ans +=
1ll * (maxx - tp) * sum[i] + (maxx - tp) * (tp + 1 + maxx) / 2);
}
if (i - f[i] == lastgap && i) i = i % lastgap + lastgap;
lastgap = i - f[i];
}
if (c[x] == P[1] && L) Mod(ans += (len[x] - maxx) * l[1]);
lastgap = 0;
f[L + 1] = 0;
for (int i = f[L++]; ~i; i = f[i]) {
if (P[1] == c[x] && l[1] <= len[x]) f[L] = 1;
if (P[i + 1] == c[x] && l[i + 1] == len[x]) {
f[L] = i + 1;
break;
}
if (i - f[i] == lastgap && i) i = i % lastgap + lastgap;
lastgap = i - f[i];
}
P[L] = c[x], sum[L] = sum[L - 1] + (l[L] = len[x]);
}
Ans[x] = ans;
for (int i = hd[x]; i; i = nx[i]) calc(to[i], L, ans);
}
int main() {
#ifndef ONLINE_JUDGE
freopen("5287.in", "r", stdin);
freopen("5287.out", "w", stdout);
#endif
scanf("%d", &n);
int opt, x;
for (int i = 1; i <= n; i++) {
scanf("%d%d", &opt, &x);
if (opt == 2) {
addr(x, i);
} else {
addr(i - 1, i);
scanf(" %c", &c[i]);
len[i] = x;
}
}
f[0] = -1;
calc(0, 0, 0);
for (int i = 1; i <= n; i++) printf("%lld\n", Ans[i]);
}
【HNOI 2019】JOJO的更多相关文章
- 【HNOI 2019】校园旅行
Problem Description 某学校的每个建筑都有一个独特的编号.一天你在校园里无聊,决定在校园内随意地漫步. 你已经在校园里呆过一段时间,对校园内每个建筑的编号非常熟悉,于是你情不自禁的把 ...
- CJOJ 1308 【HNOI 2002 】营业额统计 / CodeVS 1296 营业额统计(STL,二分)
CJOJ 1308 [HNOI 2002 ]营业额统计 / CodeVS 1296 营业额统计(STL,二分) Description Tiger最近被公司升任为营业部经理,他上任后接受公司交给的第一 ...
- 【FJWC 2019】 森林
[FJWC 2019] 森林 样例输入 0 5 1 0 0 2 样例输出 1 2 3 3 我们发现,答案就是直径加上直径上某个点出发,不经过其他直径上的点的最长链.这里的直径可以是任意一条直径. 首先 ...
- 【FJWC 2019】min
[FJWC 2019]min 题目描述 给你一张 \(n\) 个点 \(m\) 条边的无向图,走过每条边都需要花费 \(1\) 秒. 给你一个整数 \(k\) ,请你选择至多 \(k\) 个点,令经过 ...
- 【HNOI 2002】 营业额统计
[题目链接] 点击打开链接 [算法] 观察式子 : 最小波动值 = min{|该天营业额 - 之前某天的营业额|} = min{该天营业额 - 该天营业额的前驱,该天营业额的后继 - 该天营业额} 用 ...
- 【HNOI 2008】 越狱
[题目链接] 点击打开链接 [算法] 显然,越狱情况数 = 总情况数 - 不能越狱的情况数 很容易发现,总情况数 = M^N 不能越狱的情况数怎么求呢? 我们发现,不能越狱的情况,其实就是第一个人任选 ...
- 【HNOI 2003】 激光炸弹
[题目链接] https://www.lydsy.com/JudgeOnline/problem.php?id=1218 [算法] 二维前缀和 [代码] #include<bits/stdc++ ...
- 【HNOI 2004】 L语言
[题目链接] https://www.lydsy.com/JudgeOnline/problem.php?id=1212 [算法] 字典树 + dp [代码] #include<bits/std ...
- 【HNOI 2004】宠物收养所
[题目链接] https://www.lydsy.com/JudgeOnline/problem.php?id=1208 [算法] 建两棵平衡树维护领养者和宠物的特点值,这两棵平衡树支持 插入删除,查 ...
随机推荐
- Android 寻找Drawable资源的流程
寻找设备对应Drawable资源时,会先在设备对象dpi的drawable文件夹下寻找,如果没找到,会上溯到更高一级dpi文件夹下寻找,上溯最高两级.如果还是没有找到,会寻找noDensity文件夹下 ...
- php踩过的那些坑(5)浮点数计算
一.前方有坑 php在使用加减乘除等运算符计算浮点数的时候,经常会出现意想不到的结果,特别是关于财务数据方面的计算,给不少工程师惹了很多的麻烦.比如今天工作终于到的一个案例: $a = 2586; $ ...
- Spring Boot 自动装配(二)
目录 目录 前言 1.起源 2.Spring Boot 自动装配实现 2.1.@EnableAutoConfiguration 实现 2.1.1. 获取默认包扫描路径 2.1.2.获取自动装配的组件 ...
- js实现冒泡排序(bubble sort)快速排序(quick sort)归并排序(merge sort)
排序问题相信大家都比较熟悉了.用js简单写了一下几种常用的排序实现.其中使用了es6的一些语法,并且不仅限于数字--支持各种类型的数据的排序.那么直接上代码: function compare (a, ...
- ruby2.2 DevKit 安装后无法使用解决方案
windows 系统下,Ruby 的某些 gem 包需要 DevKit 才能正常安装,2.4 以后的版本可以一键安装 DevKit,之前的版本只能手动安装. 2.4 以后的可以到官网下载:https: ...
- 【nodejs原理&源码赏析(2)】KOA中间件的基本运作原理
[摘要] KOA中间件的基本运作原理 示例代码托管在:http://www.github.com/dashnowords/blogs 在中间件系统的实现上,KOA中间件通过async/await来在不 ...
- JavaScript&&jQuery创建新节点和操作属性对比
JavaScript创建新节点和操作属性 通过JavaScript原生接口创建节点,在处理上是非常复杂与繁琐的. <!DOCTYPE html> <html lang="e ...
- 学习之Redis(一)
一.redis简介 一般学习,最好先去官网,之所以建议看官网,是因为这是一手的学习资料,其他资料都最多只能算二手,一手资料意味着最权威,准确性最高.https://redis.io/topics/in ...
- Java并发编程系列-(2) 线程的并发工具类
2.线程的并发工具类 2.1 Fork-Join JDK 7中引入了fork-join框架,专门来解决计算密集型的任务.可以将一个大任务,拆分成若干个小任务,如下图所示: Fork-Join框架利用了 ...
- Python脚本之三种运行方式,你会几个?
前言本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理.作者:Jeremy_Lee123 一.交互模式下执行 Python 这种模式 ...