子序列自动机瞎吹
题面 :
多组测试数据,给出一个串 S 多次查询 T 是不是 S 的子序列。
- Step 1: 我们如果想要在一个串 S 里面,去查询一个子序列 T, 那么可以怎么做?
Solution 1:
考虑暴力双指针 \((i,j)\) ,当我们发现一个 \(S\) 中的一个新的最近的位置 \(j'\) 使得 \((i+1,j')\) 是可以匹配的,那么我们就暴力移动他这个是 \(O(n + m)\) 的,其中 \(m,n\) 分别是母串和子序列的长度。总时间是 \(O(qm +\sum n)\) 的.
Solution 2:
考虑优化暴力的过程我们可以直接记录 \(Nxt_{i,j}\) 表示位置 \(i\) 后下一个$ j$ 位置。(这个是我在做积木小赛的时候发现的),我们可以用它来 \(O(n)\) 匹配。
这就是子序列自动机,我们可以用它做到 \(O(k\sum n +m*s)\) 来匹配,其中 \(k\) 是单次往下走的时间,\(s\) 是字符集的长度。
- Step 2: Compare
这两个东西看起来差不多快啊 (退钱!),但是,我们不妨钦定 \(m\) 是 \(n ^ 2\) 级别的,然后字符集是几乎为线性的一个常数。可以解得 : 在算法 1 下的 \(n\) 可以达到 \(10^3\)级别,但不能维护多的询问,而算法 2 下处理却是游刃有余。
但是,如果没有字符集的限制,我们该如何去处理呢?
首先,观察最基础的处理方式,因为每次至多更新 1 个字符,所以 \(Nxt\) 至多更新一个元素,而我们则浪费了太多的时间在维护不变的指针上面。
然后,我们发现,这个指针是在上一个版本上建立的(要素察觉),所以我们需要维护一个支持快速维护数组的增量数据结构,也就是可持久化数组,故我们可以使用一颗可持久化线段树去维护子序列自动机。
故我们有 \(O(\sum n \log m +m\log m)\) 的时间解决该问题,当然,我们也有更简单的解决方式,我们可以拿一个 \(vector\) 存储所有元素的下标,然后二分。
#include<bits/stdc++.h>
#define mid (l + r) / 2
using namespace std;
const int MN = 1e6 + 5;
inline int read() {
int x = 0; bool f = 0; char c = getchar();
while(c < '0' || c > '9') (c == '-') ? f = 1, c = getchar() : c = getchar();
while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
return (f) ? -x : x;
}
int n, q, lim, cnt, nxt, l, opt, a[MN], b[MN] , rt[MN], ls[MN * 80], rs[MN * 80], val[MN * 80];
inline void Build(int & u, int l, int r) {
u = ++ cnt;
if (l == r) {val[u] = 0; return ;}
Build(ls[u], l, mid), Build(rs[u], mid + 1, r);
return ;
}
inline void Modify(int pre, int & u, int l, int r, int pos, int k) {
u = ++ cnt;
if (l == r) {val[u] = k; return ;}
if (pos <= mid) rs[u] = rs[pre], Modify(ls[pre], ls[u], l, mid, pos, k);
else ls[u] = ls[pre], Modify(rs[pre], rs[u], mid + 1, r, pos, k);
}
inline int Qur(int u, int l, int r, int pos) {
if (l == r) {return val[u];}
if (pos <= mid) return Qur(ls[u], l, mid, pos);
else return Qur(rs[u], mid + 1, r, pos);
}
//主席树板子
int main () {
opt = read(), n = read(), q = read(), lim = read(), Build(rt[n + 1], 1, n);
for (int i = 1; i <= n; i ++) a[i] = read(); //读入
for (int i = n; i >= 1; i --) Modify(rt[i + 1], rt[i], 1, n, a[i], i); //维护子序列自动机
for (int i = 1; i <= q; i ++) {
l = read(), nxt = 1; for (int j = 1; j <= l; j ++) b[j] = read();
for (int j = 1; j <= l; j ++) {
nxt = Qur(rt[nxt], 1, n, b[j]); // 查询下一个数位置
if (! nxt) {
printf("No\n"); break ;
}
nxt ++; //跳到这之后的位置
}
if (nxt) printf("Yes\n");
}
return 0;
}

浙公网安备 33010602011771号