你以为我只是单纯的子序列自动机吗?其实我是是子序列自动机+可持久化数组哒!
但是我看见一个大神给出了一个特别神仙又巧妙的思路!我不禁要写一篇题解记录下这神奇的思路!
而且代码贼短哦~比可持久化数组好写到不知道多少倍呢www
题意简述
给定一个序列$A$长度<=1e5,还要一些要询问的字符串$B$,长度和$<=1e6$。对于每个$B$,询问其是否是$A$的子序列。序列中的每个值都在$1e5$以内。
思路框架
在暴力匹配子序列的时候,用链表(或者vector)记录哪些询问的答案会被更新即珂。复杂度$O(1e6+1e5)$。
具体思路
这个思路是洛谷上一个叫“丁文涛2004”的神仙写的一个“链式前向星-子序列自动机”。非常强大。借这篇文章膜一下这个巨佬。
正片开始。我们知道,朴素的子序列的查询是$O(|A|\times |B|)$的。我们大约是这样写的:1
2int p=1;
F(i,1,|A|) if (A[i]==B[p]) ++p;
然后判断是否p==|B|。其中$p$表示我们当前匹配到了哪一个位置。
子序列自动机:设$nxt[i][j]$表示$i$往后第一个值为$j$的出现在哪一个位置。这样的确方便查询,但是这里值域$1e5$,这个做法显然没救了。
考虑优化第一个方法:把所有的询问放到一块来处理。那么,当我们找到一个A[i]的时候,所有满足B[p]==A[i]的p都会执行p++操作。
那么我们只要记录所有B[p]==A[i]的B[p]都在哪些位置就好了。所以,首先要用一个链表(链式前向星),把$B$的值按输入顺序串起来。然后要用一个链表把$B$按值域分类。具体的,记$head[i]$表示$B$中值为$i$的数最后一个出现的位置,$nxt[i]$表示$B$中前一个和$B[i]$相同的。然后我们令$cur=head[xxx]$,不断的令$cur=nxt[cur]$,就珂以找到$B$中所有值为$xxx$的位置了。
然后,我们在把$B$按输入顺序穿起来的时候,对于每个$B$的最后一个字符,我们令它的下一个值为$-i$,其中$i$为这个$B$在输入中的编号。这样,
“既判定了匹配到了末尾,又判定了当前所在的字符串(的编号,博主注),可谓一举两得~”(作者原话)
关于如何处理询问:
我们以$A$为基准,不断在$B$中找到匹配。对于每个$A[i]$,我们通过遍历前向星找到$B$中值为$A[i]$的位置。如果这个位置是最后一个位置,那么这个$B$就匹配成功,我们标记它是$A$的子序列。否则,我们把原来的指针指向$B[i]$的删掉,连接上$B[i+1]$,就是实现上面的$p++$操作。
然后我们发现,如果匹配成功了,也要删掉指针。那就不如在遍历到$A[i]$的时候,直接全部清空好了。(链式前向星清空很简单,只要令$head[A[i]]=0$即珂。)
代码
1 |
|