子序列自动机
# 前言
这是我第一次遇见如此简单易懂的"自动机"。
# 问题引入
给定一个长度为的正整数序列,有次询问,每次询问给定一个长度为序列,问是否为的子序列。
# 做法1 - 二分查找
由于序列中的值的个数不会超过,因此可以使用vector[i]
来存放数字i
(若数字的绝对值过大,离散化即可),当匹配时,通过二分查找下一个最小的使得
复杂度
# 做法2 - 可持久化线段树 (没必要)
朴素做法是使用数组表示之后,字符的位置,匹配时不断跳转即可。
直接维护该数组的复杂度为,显然不可。
观察可以发现,从的转移中,只有一个数据发生了变化,因而可以使用可持久化线段树维护该数组,空间复杂度,匹配的时间复杂度
Ps. 这里的字符集大小即题目中的
# 做法3 - 思维 - 桶(离线) *
我们换个角度,让每一个去匹配:给每个串一个指针(我们令的指针为,的指针为),一开始指针指向对应串的开头;不断向右移动指针,若,则右移一位。
基本思路如上,但若是每次移动都要扫一遍,时间复杂度为,显然不行。
考虑到字符集大小较小,我们可以使用桶来储存对应的指针:令内装所有的指针,当,我们令桶内的所有指针+1,并将指针插入到对应的新桶中,接着清空桶。
又因为比较小,可以使用前向星来维护各个桶(见AC代码)。当然,也可以手动维护队列,稍微麻烦一点罢了。
AC代码
#include <bits/stdc++.h>
#define Android ios::sync_with_stdio(false), cin.tie(NULL)
using namespace std;
const int maxn = 1e5 + 100, maxl = 1e6 + 1000;
int head[maxn], nxt[maxl], val[maxl], vcnt = 0;
int ptr[maxn], ans[maxn];
vector<int> arr[maxn];
void add(int x, int v){
nxt[vcnt] = head[x], val[vcnt] = v;
head[x] = vcnt++;
}
void solve(){
int n, q, m;
cin >> n >> n >> q >> m;
for(int i=0; i<=m; i++) head[i] = -1;
arr[0].resize(n);
for(int i=0; i<n; i++) cin >> arr[0][i];
for(int i=1, l; i<=q; i++){
cin >> l;
arr[i].resize(l);
for(int j=0; j<l; j++) cin >> arr[i][j];
add(arr[i][0], i);
}
for(int i=0; i<n; i++){
int v = arr[0][i], from = head[v];
head[v] = -1; // 清空
for(int p=from; ~p; p=nxt[p]){
int k = val[p];
ptr[k]++;
if(ptr[k] == arr[k].size()){
ans[k] = 1;
}else{
// 使用前向星,无需判断arr[k][ptr[k]]==arr[k][k-1]的情况
add(arr[k][ptr[k]], k);
}
}
}
for(int i=1; i<=q; i++){
cout << (ans[i]?"Yes":"No") << '\n';
}
}
signed main(){
Android;
solve();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
上次更新: 2021/02/24, 03:37:30