P5826 【模板】子序列自动机

题目链接

P5826 【模板】子序列自动机

【模板】子序列自动机

题目背景

本题中,若 \(x\)\(y\) 的子序列,则等价于存在一个单调递增序列 \(z\),满足 \(|z| = |x|\)\(z_{|x|} \leq |y|\),且 \(\forall i \in [1, ~|x|],~y_{z_i} = x_i\)。其中 \(|x|,~|y|,~|z|\) 分别代表序列 \(x,~y,~z\) 的长度,\(x_i,~y_i,~z_i\) 分别代表序列 \(x,~y,~z\) 的第 \(i\) 项。

这是一道在 yLOI2020 备选题里被毙掉的题目。

题目描述

给定一个长度为 \(n\) 的正整数序列 \(a\) ,有 \(q\) 次询问,第 \(i\) 次询问给定一个长度为 \(L_i\) 的序列 \(b_i\),请你判断 \(b_i\) 是不是 \(a\) 的子序列。序列 \(a\) 和所有 \(b_i\) 中的元素都不大于一个给定的正整数 \(m\)

输入格式

每个测试点有且仅有一组数据。

输入的第一行是四个用空格隔开的整数,分别代表 \(type,~n,~q,~m\)。其中 \(type\) 代表测试点所在的子任务编号,其余变量的含义见【题目描述】。

输入的第二行是 \(n\) 个用空格隔开的整数,第 \(i\) 个数字代表序列 \(a\) 的第 \(i\) 个元素 \(a_i\)

\(3\) 行至第 \((q + 2)\) 行,每行代表一次询问。第 \((i + 2)\) 行的输入格式为:

  • \((i + 2)\) 行的行首有一个整数 \(l_i\),代表第 \(i\) 次询问的序列长度。一个空格后有 \(l_i\) 个用空格隔开的整数。该行的第 \((j + 1)\) 个整数代表序列 \(b_i\) 的第 \(j\) 个元素 \(b_{i, j}\)

输出格式

对于每次询问,输出一行一个字符串,若给定的序列是 \(a\) 的子序列,则输出 Yes,否则输出 No

样例 #1

样例输入 #1

0 5 5 5
1 3 2 2 4
3 1 5 2
2 3 2
3 1 2 3
3 1 2 4
5 1 3 2 2 4

样例输出 #1

No
Yes
No
Yes
Yes

提示

样例 1 解释

  • 对于第一次询问,原序列中没有 \(5\),所以给定序列显然不是原序列的子序列。
  • 对于第二次询问,存在两个合法的序列 \(z\),分别是 \(\{2,~3\}\)\(\{2,~4\}\)。即 \(a_{2} = b_{2, 1},~a_3 = b_{2, 2}\)\(a_2 = b_{2, 1},~a_{4} = b_{2, 2}\)。这里 \(b_{i, j}\) 代表序列 \(b_i\) 的第 \(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 \leq 100\)\(\sum_{i = 1}^{q} l_i \leq 10^3\)
  • Subtask 2(35 points):\(type = 2\)\(n,q \leq 10^5\)\(m \leq 26\)\(\sum_{i = 1}^{q} l_i \leq 10^6\)
  • Subtask 3(45 points):\(type = 3\)\(n,q,m \leq 10^5\)\(\sum_{i = 1}^q L_i \leq 10^6\)

对于全部的测试点,保证 \(1 \leq n, m, q \leq 10^5\)\(1 \leq a_i, b_{i, j} \leq m\)\(1 \leq l_i \leq 10^6\)\(\sum_{i = 1}^{q} l_i \leq 10^6\)

提示

  • 请注意常数因子对程序效率造成的影响。

  • 本题输入规模较大,请注意数据读入对程序效率造成的影响。

  • 请注意输入第一行的顺序为先输入询问次数 \(q\),再输入序列元素最大值 \(m\)

解题思路

序列自动机

考虑暴力的情况:\(nxt[i][j]\) 表示当前从母串 \(i\) 开始,匹配 匹配串 \(j\) 这个字符的第一次出现的位置,从后往前更新 \(nxt[i][j]\) 即可,而且 \(nxt[i]\)\(nxt[i+1]\) 只有一个 \(j\) 的位置不同,这就启发可以用主席树维护这些信息,即建立 \(n\) 棵线段树,每次修改时只会在一处位置发生变化,即值域 \(a[i]\) 这个位置要变为 \(i\),同时主席树的历史根节点编号即 \(i\) 这个下标,查询时只要查询对应当前根节点表示的线段树中是否存在对应值对应的位置

  • 时间复杂度:\(O((n+\sum_{i=1}^q l_i) \times logn)\)

二分

\(a\) 的下标用桶存下来,每次读入子序列时,都从当前值满足条件的最前面开始搜索,如果搜不到则一定不是子序列

  • 时间复杂度:\(O(\sum_{i=1}^q l_i \times logn)\)

代码

  • 序列自动机
// Problem: P5826 【模板】子序列自动机
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P5826
// Memory Limit: 500 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

// %%%Skyqwq
#include <bits/stdc++.h>
 
//#define int long long
#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;
}
  • 二分
// Problem: P5826 【模板】子序列自动机
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P5826
// Memory Limit: 500 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

// %%%Skyqwq
#include <bits/stdc++.h>
 
//#define int long long
#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=1e5+5;
vector<int> a[N];
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",&x);
    	a[x].pb(i);
    }
    while(q--)
    {
    	bool res=true;
    	int pos=0;
    	for(scanf("%d",&l);l;l--)
    	{
    		scanf("%d",&x);
    		pos=upper_bound(a[x].begin(),a[x].end(),pos)-a[x].begin();
    		if(pos<a[x].size())pos=a[x][pos];
    		else
    			res=false;
    	}
    	puts(res?"Yes":"No");
    }
    return 0;
}
posted @ 2022-10-29 18:20  zyy2001  阅读(25)  评论(0编辑  收藏  举报