子序列自动机瞎吹

题面 :

多组测试数据,给出一个串 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;
}

posted @ 2022-03-31 14:00  Cust10  阅读(131)  评论(0)    收藏  举报