【可持久化线段树】【P5826】【模板】子序列自动机

Description

给定一个序列 \(A\),有 \(q\) 次询问,每次询问一个序列 \(B\) 是不是 \(A\) 的子序列

Limitations

序列 \(A\) 长度不超过 \(10^5\),询问序列长度之和不超过 \(10^6\),询问次数不超过 \(10^5\)

Solution

题外话:有关这道题的难度,我觉得大概到不了紫色,但是可持久化线段树的板子是紫色的,所以就设成了紫色

Algorithm \(1\)

考虑对于一个询问序列 \(B\),设其与 \(A\) 的最长公共子序列在 \(A\) 中的下标序列为 \(Z\),显然当且仅当 \(Z\) 的长度为 \(|B|\) 时,\(B\) 是 \(A\) 的子序列。合法的序列 \(Z\) 可能会有多个,但是只要我们找到了字典序最小的长度为 \(|B|\) 的序列 \(Z\),就可以说明 \(B\) 是 \(A\) 的子序列,否则不是。

考虑寻找字典序最小的 \(Z\) 可以贪心的选择,即对于 \(B\) 的每个前缀,可以求出其对应的 \(Z\) 序列的最后一位最小是多少,当 \(B\) 的前缀新增一个数字时,只需要在 \(A\) 中从当前 \(Z\) 序列最后一位的值的位置开始继续向后扫描,扫到第一个等于新增数字的位置,即是新的 \(Z\) 序列的最后一位。而如果扫描到了 \(A\) 的最后也没有找到,则意味着不存在合法的 \(Z\) 序列,因此 \(B\) 不是 \(A\) 的子序列。

这样的话每次询问时,最多扫描 \(A\) 一次,因此总时间复杂度为 \(O(nq + \sum L)\),可以通过 Subtask \(1\),期望得分 \(20~pts\)

Algorithm \(2\)

考虑对 \(A\) 建立一个子序列自动机,用来识别 \(A\) 的所有子序列。

同样运用 Algorithm 1 中的思想,对于一个字符串\(B\),我们只要找到了其与 \(A\) 的最长公共子序列在 \(A\) 中的字典序最小的下标序列 \(Z\),就可以说明 \(B\) 是 \(A\) 的子序列。那么对于 \(A\) 的每一位而言,在其需要新匹配一个数字时,应该转移到 \(A\) 后面第一个为该数字的位置,显然这样才能保证 \(Z\) 序列的字典序是最小的。因此我们的转移应该对每个位置维护加入一个数字以后它后面第一个为该数字的位置。

考虑我们对 \(A\) 从后向前逐位建立自动机,对于第 \(i\) 位而言,第 \(i - 1\) 位加入 \(A_i\) 应该转移到 \(i\),而加入其它数字应该转移到 \(A_i\) 加入该数字后转移到的位置。因此有伪代码

for i : m do
trans[n][i] <- -1
end
for i = n : 1 do
for j = 1 : m do
trans[i - 1][j] <- trans[i][j]
end
trans[i - 1][A[i]] <- i
end

其中 \(n\) 代表 \(A\) 的长度,\(m\) 代表 \(A\) 中的最大值,\(trans\) 是一个二维数组,代表这个自动机。

而对一个字符串 \(B\) 进行匹配时,只需要将 \(B\) 顺着自动机的转移跑一遍,若没有跑出自动机,则 \(B\) 是 \(A\) 的子序列,否则不是。

Function check:
pos <- 0
ret <- true
for i = 1 : L do
pos <- trans[pos][B[i]]
if pos == -1 then
ret <- false
break
endif
end
return ret
end Func

注意到这样构造自动机的时间复杂度为 \(O(nm)\),匹配的复杂度为 \(O(\sum L)\),因此总时间复杂度 \(O(nm + \sum L)\),可以通过 Subtask \(1\),\(2\),期望得分 \(55~pts\)。

Algorithm \(3\)

注意到构造自动机时,第 \(i\) 位与第 \(i - 1\) 位只有 \(A_i\) 一项不一样,第 \(i - 1\) 位的转移可以看做第 \(i\) 位的转移的基础上修改了一个位置,因此我们可以从后向前使用可持久化线段树来维护每个位置的转移数组,这样建立自动机的时间复杂度为 \(O(n \log m)\),匹配的时间复杂度为 \(O(\sum L \log m)\)。总时间复杂度 \(O((n + \sum L) \log m)\),可以通过全部的 Subtask,期望得分 \(100~pts\)。

Code

Algorithm \(2\)

代码来自 @_皎月半洒花

#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream> #define MAXN 200010 using namespace std ; int L, N, M, Q, S[MAXN], nxt[MAXN][102] ; void build(){
for (int i = 1 ; i <= M ; ++ i)
nxt[L + 2][i] = nxt[L + 1][i] = L + 2 ;
for (int i = L ; i ; -- i)
memcpy(nxt[i - 1], nxt[i], sizeof(nxt[i])), nxt[i - 1][S[i]] = i ;
}
int qr(){
char c = getchar() ;
int res = 0 ; while (!isdigit(c)) c = getchar() ;
while (isdigit(c)) res = (res << 1) + (res << 3) + c - 48, c = getchar() ;
return res ;
}
int main(){
int i, j, k, emm ;
cin >> emm >> N >> Q >> M ; L = N ;
for (i = 1 ; i <= L ; ++ i) scanf("%d", &S[i]) ; build() ;
for (i = 1 ; i <= Q ; ++ i){
N = qr() ; int st = 0, ans = 0 ;
for (j = 1 ; j <= N ; ++ j){
k = qr(), st = nxt[st][k] ;
if (!st){
while (j < N)
++ j, emm = qr() ;
ans = 1 ;
}
// cout << st << endl ;
}
printf(ans ? "No\n" : "Yes\n") ;
}
return 0 ;
}

Algorithm \(3\)

#include <cstdio>

template <typename T>
inline void qr(T &x) {
char ch;
do ch = getchar(); while ((ch > '9') || (ch < '0'));
do x = x * 10 + (ch ^ 48), ch = getchar(); while ((ch >= '0') && (ch <= '9'));
} const int maxn = 100005; struct Tree {
Tree *ls, *rs;
int l, r, v; Tree(const int L, const int R) : l(L), r(R), v(-1) {
if (l != r) {
int mid = (l + r) >> 1;
ls = new Tree(l, mid);
rs = new Tree(mid + 1, r);
}
} Tree(Tree *pre, const int P, const int V) : l(pre->l), r(pre->r), v(0) {
if (l == r) {
v = V;
} else {
if (pre->ls->r >= P) {
rs = pre->rs;
ls = new Tree(pre->ls, P, V);
} else {
ls = pre->ls;
rs = new Tree(pre->rs, P, V);
}
}
} int query(const int x) {
if (this->l == this->r) {
return this->v;
} else {
return (this->ls->r >= x) ? this->ls->query(x) : this->rs->query(x);
}
}
};
Tree *rot[maxn]; int tp, n, q, m;
int MU[maxn]; int main() {
qr(tp); qr(n); qr(q); qr(m);
rot[n] = new Tree(1, m);
for (int i = 1; i <= n; ++i) {
qr(MU[i]);
}
for (int i = n; i; --i) {
rot[i - 1] = new Tree(rot[i], MU[i], i);
}
for (int L, x, pos; q; --q) {
L = pos = 0; qr(L);
while ((L--) && (pos != -1)) {
x = 0; qr(x);
if ((pos = rot[pos]->query(x)) == -1) {
while (L--) {
qr(x);
}
break;
}
}
puts((~pos) ? "Yes" : "No");
}
return 0;
}

appreciation

感谢验题人:@_皎月半洒花 @water_lift

感谢本文的审核与校对:@Dusker

【可持久化线段树】【P5826】【模板】子序列自动机的更多相关文章

  1. Luogu P3919 【模板】可持久化数组 可持久化线段树

    其实就是可持久化线段树的模板题线段树不会看这里 #include<bits/stdc++.h> ; using namespace std; ]; ],rc[N*],val[N*],cnt ...

  2. LuoguP3834 【模板】可持久化线段树 1(主席树)|| 离散化

    题目:[模板]可持久化线段树 1(主席树) 不知道说啥. #include<cstdio> #include<cstring> #include<iostream> ...

  3. Luogu P3919【模板】可持久化数组(可持久化线段树/平衡树)

    题面:[模板]可持久化数组(可持久化线段树/平衡树) 不知道说啥,总之我挺喜欢自己打的板子的! #include<cstdio> #include<cstring> #incl ...

  4. 洛谷 P3919 【模板】可持久化数组(可持久化线段树/平衡树)-可持久化线段树(单点更新,单点查询)

    P3919 [模板]可持久化数组(可持久化线段树/平衡树) 题目背景 UPDATE : 最后一个点时间空间已经放大 标题即题意 有了可持久化数组,便可以实现很多衍生的可持久化功能(例如:可持久化并查集 ...

  5. 【洛谷P3834】(模板)可持久化线段树 1(主席树)

    [模板]可持久化线段树 1(主席树) https://www.luogu.org/problemnew/show/P3834 主席树支持历史查询,空间复杂度为O(nlogn),需要动态开点 本题用一个 ...

  6. 洛谷——P3919 【模板】可持久化数组(可持久化线段树/平衡树)

    P3919 [模板]可持久化数组(可持久化线段树/平衡树) 题目背景 UPDATE : 最后一个点时间空间已经放大 标题即题意 有了可持久化数组,便可以实现很多衍生的可持久化功能(例如:可持久化并查集 ...

  7. [POJ2104] 区间第k大数 [区间第k大数,可持久化线段树模板题]

    可持久化线段树模板题. #include <iostream> #include <algorithm> #include <cstdio> #include &l ...

  8. luogu P3919 [模板]可持久化数组(可持久化线段树/平衡树)(主席树)

    luogu P3919 [模板]可持久化数组(可持久化线段树/平衡树) 题目 #include<iostream> #include<cstdlib> #include< ...

  9. 洛谷P3834【模板】可持久化线段树 1(主席树)

    题目背景 这是个非常经典的主席树入门题--静态区间第K小 数据已经过加强,请使用主席树.同时请注意常数优化 题目描述 如题,给定N个正整数构成的序列,将对于指定的闭区间查询其区间内的第K小值. 输入输 ...

随机推荐

  1. 容斥原理--计算并集的元素个数 URAL 1091

    在计数时,必须注意没有重复,没有遗漏.为了使重叠部分不被重复计算,人们研究出一种新的计数方法,这种方法的基本思想是:先不考虑重叠的情况,把包含于某内容中的所有对象的数目先计算出来,然后再把计数时重复计 ...

  2. 机器学习之Artificial Neural Networks

    人类通过模仿自然界中的生物,已经发明了很多东西,比如飞机,就是模仿鸟翼,但最终,这些东西会和原来的东西有些许差异,artificial neural networks (ANNs)就是模仿动物大脑的神 ...

  3. 深入浅出 REST(转)

    文章讲的不错,更具体一些,对实践的指导意义更强 原文:https://www.infoq.cn/article/rest-introduction/ 不知你是否意识到,围绕着什么才是实现异构的应用到应 ...

  4. Java学习:File类中的过滤器接口

    javaIO类的File类应用:过滤器接口 FilenameFilter和FileFilter都是用来过滤文件的 例如: 过滤以.jpg或者.java结尾的文件. 通过看他们的源码: 通过使用File ...

  5. 2019-11-29-WPF-客户端开发需要知道的触摸失效问题

    原文:2019-11-29-WPF-客户端开发需要知道的触摸失效问题 title author date CreateTime categories WPF 客户端开发需要知道的触摸失效问题 lind ...

  6. c#多个按钮执行同一类事件-按钮按下和弹起

    首先在Winform中添加一个Button控件,在属性里面为控件添加鼠标按下和弹起事件(不要双击按钮,在属性里面添加) 再添加其他几个按钮控件,在控件的属性里面为鼠标按下和弹起添加已定义好处理函数(M ...

  7. drf--搜索、过滤、排序组件

    目录 drf--搜索.过滤.排序组件 过滤 DjangoFilterBackend 自定义过滤器django-filter模块 自定义过滤类 搜索SearchFilter 排序OrderingFilt ...

  8. 写给自己的 SOA 和 RPC 理解

    1.SOA SOA(Service-Oriented Architecture)面向服务架构,将应用程序不同功能单元(称为服务)进行拆分,并通过这些服务之间定义良好的接口和契约联系起来. SOA 不是 ...

  9. SVG跟随父级DIV自适应

    后台返回过来的是这样的SVG标签 <svg width="100%" height="100%" version="1.1" xmln ...

  10. 3 CVE-2017-11882漏洞分析

    CVE-2017-11882漏洞分析 操作系统:Windows7 32/64位 专业版.Linux 软件:office 2003 sp3 工具:OD.IDA.Python模块.msfconsole 1 ...