题目:
给你a和b两个字符串,问b是不是a的子序列?
- 题解:
参考题解
注意看,子序列不是子串,两者含义不一样
一个字符串 s 被称作另一个字符串 S 的子串,表示 s 在 S中出现了。 一个字符串 s被称作另一个字符串 S 的子序列,说明从序列 S
通过去除某些元素但不破坏余下元素的相对位置(在前或在后)可得到序列 s 。
可以理解成:abc是abcd的子串,abc是adbewc的子序列,而非子串
求子串可以用kmp
那求子序列就用今天要讲的序列自动机
字符串A:abcdefgh
字符串B:aez
当a匹配过后,再去枚举后面的bcd没有意义,因为我们要找的是A中是否要aez三个字符,并且是这样的顺序,无须是连着的。如果我们能在a之后直接跳到a后面的一个e,再跳到e后面第一个z,等到子串B遍历完,或者在a中找不到了,我们就可以结束啦。
注意:我们在往后跳找字母时,比如a后面有很多个e,那么我们选第一个,因为如果第一个不行的话,那第二个第三个也白瞎,我们选的越往前其实选择空间就越大
仔细想想怎么能实现跳跃查找这个呢:
开个数组next[i][j]表示主串第i个字母之后的第一个‘a’+j的坐标,
next的维护只需要从后向前扫描主串,到第i位时维护一个数组last[j],j表示对应的‘a’~‘z’,last反映的是最靠前的字母j在哪里,然后把值赋给next就ok了(也可以不用last)
这样查找时顺着走就可以了
每次匹配B复杂度是O(|B|),
总复杂度O(26|A|+|B|),因为A要用26个字母都循环一遍所以是26|A|
可以通过下图理解
模板:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 1e6 + 10; int n, t, next[maxn][30]; char s[maxn], str[maxn]; int main() { scanf("%s", s+1); int len = strlen(s+1); for(int i=len; i; i--) { for(int j=0; j<26; j++) { next[i-1][j] = next[i][j]; } next[i-1][s[i]-'a'] = i; } scanf("%d", &t); while(t--){ scanf("%s", str); int lenc = strlen(str), f = 0; int now=0; for(int i=0; i<lenc; i++){ now = next[now][str[i]-'a']; if(!now) { f = 1; break; } } if(f) puts("No"); else puts("Yes"); } }