题目链接
【模板】子序列自动机
题目背景
本题中,若 x 是 y 的子序列,则等价于存在一个单调递增序列 z,满足 |z|=|x|,z|x|≤|y|,且 ∀i∈[1, |x|], yzi=xi。其中 |x|, |y|, |z| 分别代表序列 x, y, z 的长度,xi, yi, zi 分别代表序列 x, y, z 的第 i 项。
这是一道在 yLOI2020
备选题里被毙掉的题目。
题目描述
给定一个长度为 n 的正整数序列 a ,有 q 次询问,第 i 次询问给定一个长度为 Li 的序列 bi,请你判断 bi 是不是 a 的子序列。序列 a 和所有 bi 中的元素都不大于一个给定的正整数 m。
输入格式
每个测试点有且仅有一组数据。
输入的第一行是四个用空格隔开的整数,分别代表 type, n, q, m。其中 type 代表测试点所在的子任务编号,其余变量的含义见【题目描述】。
输入的第二行是 n 个用空格隔开的整数,第 i 个数字代表序列 a 的第 i 个元素 ai。
第 3 行至第 (q+2) 行,每行代表一次询问。第 (i+2) 行的输入格式为:
- 第 (i+2) 行的行首有一个整数 li,代表第 i 次询问的序列长度。一个空格后有 li 个用空格隔开的整数。该行的第 (j+1) 个整数代表序列 bi 的第 j 个元素 bi,j。
输出格式
对于每次询问,输出一行一个字符串,若给定的序列是 a 的子序列,则输出 Yes
,否则输出 No
。
样例 #1
样例输入 #1
样例输出 #1
提示
样例 1 解释
- 对于第一次询问,原序列中没有 5,所以给定序列显然不是原序列的子序列。
- 对于第二次询问,存在两个合法的序列 z,分别是 {2, 3} 和 {2, 4}。即 a2=b2,1, a3=b2,2 或 a2=b2,1, a4=b2,2。这里 bi,j 代表序列 bi 的第 j 个元素,序列 z 的含义见【题目背景】,下同。
- 对于第三次询问,不存在合法的序列 z。
- 对于第四次询问,存在两个合法的序列 z,分别是 {1, 3, 5} 和 {1, 4, 5}。
- 对于第五次询问,存在一个合法的序列 z,为 {1, 2, 3, 4, 5}。
数据范围与约定
本题采用多测试点捆绑测试,共有 3 个子任务。
- Subtask 1(20 points):type=1,n,q,m≤100,∑qi=1li≤103。
- Subtask 2(35 points):type=2,n,q≤105,m≤26,∑qi=1li≤106。
- Subtask 3(45 points):type=3,n,q,m≤105,∑qi=1Li≤106。
对于全部的测试点,保证 1≤n,m,q≤105,1≤ai,bi,j≤m,1≤li≤106,∑qi=1li≤106。
提示
解题思路
序列自动机
考虑暴力的情况:nxt[i][j] 表示当前从母串 i 开始,匹配 匹配串 j 这个字符的第一次出现的位置,从后往前更新 nxt[i][j] 即可,而且 nxt[i] 和 nxt[i+1] 只有一个 j 的位置不同,这就启发可以用主席树维护这些信息,即建立 n 棵线段树,每次修改时只会在一处位置发生变化,即值域 a[i] 这个位置要变为 i,同时主席树的历史根节点编号即 i 这个下标,查询时只要查询对应当前根节点表示的线段树中是否存在对应值对应的位置
- 时间复杂度:O((n+∑qi=1li)×logn)
二分
将 a 的下标用桶存下来,每次读入子序列时,都从当前值满足条件的最前面开始搜索,如果搜不到则一定不是子序列
- 时间复杂度:O(∑qi=1li×logn)
代码
#include <bits/stdc++.h>
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
template <typename T> void inline read(T &x) {
int f = 1; x = 0; char s = getchar();
while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
x *= f;
}
const int N=5e5+5,M=N*4+17*N;
int a[N],root[N],idx;
struct Tr
{
int l,r,v;
}tr[M];
int build(int l,int r)
{
int p=++idx;
if(l==r)return p;
int mid=l+r>>1;
tr[p].l=build(l,mid),tr[p].r=build(mid+1,r);
return p;
}
int insert(int p,int l,int r,int x,int v)
{
int q=++idx;
tr[q]=tr[p];
if(l==r)
{
tr[q].v=v;
return q;
}
int mid=l+r>>1;
if(x<=mid)tr[q].l=insert(tr[p].l,l,mid,x,v);
else
tr[q].r=insert(tr[p].r,mid+1,r,x,v);
return q;
}
int ask(int p,int l,int r,int x)
{
if(l==r)return tr[p].v;
int mid=l+r>>1;
if(x<=mid)return ask(tr[p].l,l,mid,x);
return ask(tr[p].r,mid+1,r,x);
}
int main()
{
int t,n,q,m,x,l;
scanf("%d%d%d%d",&t,&n,&q,&m);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
root[n+1]=build(1,m);
for(int i=n;i;i--)root[i]=insert(root[i+1],1,m,a[i],i);
while(q--)
{
bool res=true;
int nxt=1;
for(scanf("%d",&l);l;l--)
{
scanf("%d",&x);
nxt=ask(root[nxt],1,m,x);
if(!nxt)res=false;
else
nxt++;
}
puts(res?"Yes":"No");
}
return 0;
}
__EOF__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!