【 算 法 讲 解 】 \color{green}{【算法讲解】} 【算法讲解】
【 模 板 链 接 】 : \color{blue}{【模板链接】:} 【模板链接】: 洛谷P5826 【模板】子序列自动机
【
算
法
目
标
】
:
\color{blue}{【算法目标】:}
【算法目标】:
【
算
法
略
讲
】
:
\color{blue}{【算法略讲】:}
【算法略讲】: 首先,我们来看看暴力如何完成这道题。很简单,拿一个指针,从头扫描到尾,单次查找时间复杂度
O
(
n
)
O(n)
O(n)。
回归正题,什么是子序列自动机?很简单,就是一个序列而已。记 p o s x pos_x posx保存数字 x x x的位置。比如序列为 [ 1 , 2 , 1 , 5 , 5 ] [1,2,1,5,5] [1,2,1,5,5],则 p o s 1 = [ 1 , 3 ] pos_1=[1,3] pos1=[1,3]。
这个东西有什么用呢?我们可以用它来优化查找的过程。什么意思呢?比如我们现在找到了
l
s
t
lst
lst这么个位置上,我们想找一个数字
5
5
5,我们就只需找到
p
o
s
5
pos_5
pos5中
>
l
s
t
>lst
>lst的一个位置即可。
等等,如果有很多位置的话,我们找哪个呢?很简单,找第一个,因为它留给后面匹配的空间最大,最容易满足条件。
因为 p o s pos pos是递增的,所以这一过程我们可以用二分实现,总时间复杂度 O ( n + ( ∑ i = 1 q L i ) × log m ) O(n+(\sum\limits_{i=1}^{q} L_i)\times \log m) O(n+(i=1∑qLi)×logm)。
还有,直接开数组会MLE
哦,怎么办?直接用一个vector
即可。
【 代 码 】 : \color{blue}{【代码】:} 【代码】:
#define gc getchar()
#define g(c) isdigit(c)
inline int read(){
char c=0;int x=0;bool f=0;
while (!g(c)) f=c=='-',c=gc;
while (g(c)) x=x*10+c-48,c=gc;
return f?-x:x;
}
const int N=1e5+100;
vector<int> pos[N];
vector<int>::iterator t;
int n,q,m,a[N],lst;bool sign;
int main(){
read();n=read();q=read();read();
for(int i=1;i<=n;i++)
pos[read()].push_back(i);//计算pos数组
for(int test=1;test<=q;test++){
m=read();sign=true;lst=0;
for(int i=1;i<=m;i++)
a[i]=read();//输入待匹配数组
for(int i=1;i<=m;i++){
t=upper_bound(pos[a[i]].begin(),pos[a[i]].end(),lst);
if (t==pos[a[i]].end()){sign=false;break;}
lst=*t;//指针t对应的位置就是lst的新值
}
puts(sign?"Yes":"No");
}
return 0;
}