南昌ICPC2019网络预选M---Subsequence[判断非连续子串]

2 篇文章 0 订阅
1 篇文章 0 订阅

题目大意:

先给你一个母串,再给你 n 个字符串,让你判断这 n 个字符串是否为母串的非连续子串;

样例输入

abcdefg
3
abc
adg
cba

样例输出

YES
YES
NO

思路:

最简单的方法肯定就是拿每个字符串和母串比对,看是否为它的非连续子串。先在母串中找到给定字符串的第一项,再在那个位置的后面寻找第二项。。。以此类推。这肯定会超时。我们假设母串的长度为 s1 ,要判断的字符串长度为 s2 ,总共有 n 个字符串,那么最差的情况就是 o(s1*n) 时间复杂度超过 10e10

正确思路:
1.建图

比赛时思虑万千,才想出来,最后还因为大小写wa了几发,差点没做出来,可以说是菜的真实。
一开始是想用字典树把母串所有的非连续子串保存下来,这样每次判断的时候就是 o(s2) 的复杂度了。(不知道字典树没关系,大概理解思路,也可以看我之前的博客)如下:
在这里插入图片描述

这样从根节点到任意节点之间的字符串都是母串的非连续子串。
但显然建立字典树的的时间和空间复杂度都是 o(s1^2) 。总的时间复杂度为 o(s1^2+n*s1)数据范围教我做人。 这样不仅不能节省时间,还会使内存开销爆炸。
但这样想并不是没有意义,至少我们降低了每次比对的时间。我们仔细观察会字典树中发现有大量重复的地方。如下:
在这里插入图片描述
然后我们可以看出,对于某个确定的节点来说它的子树与它前面的节点没关系,全都是相同的。这样我们就可以用一幅有向图来表示,如下:
在这里插入图片描述
那么在任意两个节点之间的任意一条连线都是母串的一个非连续子串。
可是问题又来了:这样我们虽然节省了很大一部分空间但是,我们有 s1 个节点,通过观察我们也能知道,任意两个节点都有一条有向边,就一共 s1^2 条边。爆炸!!!
这时候让我们再回想一下,最朴素的暴力算法。(我爱暴力算法) 每次找后面第一个对应的字母。
对!!!!没错!!
我们根本不需要把任意的两个点连接起来,对任意一个节点我们只需要链接它和它后面第一个a,b,c…这样我们依旧可以确定所有的非连续子串;
如下:

在这里插入图片描述
与字典树相同,用边来储存信息,这样在节点我们就不用遍历每条边,直接通过下标进行转移。这样时间复杂度被降为 o(26*s1+n*s2) ,空间复杂度被降为 o(26*s1+s2)

2.读入挂+二分;

在朴素算法中,每次确定下一个字母的位置是时间的主要开销。这时候我们对每个字母在字符串出现的位置单独用一个数组记录下来,每次用二分的方法查找,以此来提高效率。再加上读入挂,就可以保证时间;
(这个思路也只是听别人的,博主并没有这样写过,所以不附上代码了。ps:之前从没听说过读入挂,和别人讨论时才知道有这个东西。菜的真实。)

思路1的AC代码:

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <string>
using namespace std;
char s[100005];
struct node{ 
	char ch;
	int next[30];
}a[100500];
int head[30]={0};
int st[30]={0};
int map[30];
int nnew=1;
void set(int len){
	int i,j;
	for(i=1;i<=len;i++) 
	{
		s[i]=s[i]-'a'+1;
		if(!st[s[i]]) st[s[i]]=i;
	}
	for(i=1;i<=26;i++){
		map[i]=a[0].next[i]=st[i];
	}
	for(i=1;i<=len;i++){
		map[s[i]]++;
		while(s[map[s[i]]]!=s[i]&&map[s[i]]<=len) map[s[i]]++;
		
		a[i].ch=s[i];
		for(j=1;j<=26;j++){
			a[i].next[j]=map[j];
		}
	}
	return ;
}

int main()
{
	int i,j;
	scanf("%s",s+1);
	int la=strlen(s+1);
	int n;
	scanf("%d",&n);
	
	set(la);//printf("%d \n",la);
	//print();
	
	char b[1005];
	while(n--)
	{
		
		scanf("%s",b+1);
		int lb=strlen(b+1);
		for(i=1;i<=lb;i++) b[i]=b[i]-'a'+1;
		int flag=1;
		for(i=1,j=0;i<=lb;i++){
			if(a[j].next[b[i]]){
				
				j=a[j].next[b[i]];
				if(j>la) {
					flag=0;
					break;
				}
			}
			else{
				flag=0;
				break;
			}
		}
		
		
		if(flag)
			printf("YES\n");
		else
			printf("NO\n");
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值