- 给定一个长度为(n)的文本串,字符集大小为(m)。
- (q)次询问,每次给定一个字符串,询问是否为文本串的一个子序列。
- (n,m,qle10^5)
思想核心:贪心
其实就这题而论,子序列自动机是一个非常简单易懂的算法,闲得无聊来水一水。
核心思想是贪心,就是考虑我们每次找到尽可能近的字符匹配一定是最优的。
方法一:主席树
由于常见字符串问题的字符集大小是(26),所以相信大家都写过一个东西,就是记录(S_{i,c})表示第(i)位之后第一个字符(c)所在位置。
要维护这个只需从后往前枚举,每次(S_i)继承(S_{i+1}),并更新(S_{i,a_{i+1}}=i+1)。
这题字符集很大,考虑继承操作就相当于是一个版本的赋值,随后我们进行的是在新版本上的一个单点修改。
发现这就是一个用主席树实现可持久化数组的过程,非常板子。
方法二:(vector)+二分
直接对于每个字符集开一个(vector),要找到第(i)位之后第一个字符(c),只要在字符(c)对应的(vector)中求出(upper\_bound(i))即可。
而且把(vector)换成(set)甚至能实现带修,这是主席树做不到的。所以说主席树直接被全方位吊打。
代码:(O(nlogn))
当然是方法二啦,不会真的有人会去写主席树吧。
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
using namespace std;
int n,m;vector<int> S[N+5];vector<int>::iterator it;
int main()
{
RI Qt,i,x,y;for(scanf("%d%d%d%d",&x,&n,&Qt,&m),i=1;i<=n;++i) scanf("%d",&x),S[x].emplace_back(i);//对每种字符维护个vector
RI t;W(Qt--) {for(scanf("%d",&t),x=0;t;--t) scanf("%d",&y),
~x&&(x=(it=upper_bound(S[y].begin(),S[y].end(),x))==S[y].end()?-1:*it);puts(~x?"Yes":"No");}//利用upper_bound查找下一位
return 0;
}