Subsequence(字符串的子序列查询)

题目大意:

        给一个模板字符串s,再给一堆子查询s1,每个子查询是一个字符串,判断子查询是不是模板串的子序列。注意是子序列,不是子串,子序列即元素之间不一定要相连。

        本题很容易想到的一种方法就是每次查询都遍历模板串,这种方法虽然正确但一定会超时。所以我们就改变思路,每次查询都遍历查询串,查询串中的每个字母在模板串中肯定不止出现一次,看看能不能找到一种情况使子查询串中的每个字母有序地在模板串中出现。

数据结构:

  1. 二维数组vv,每行储存一种字母在模板串中所有出现过的位置,一共26行。例如:s=“abcabca”,则vv[0]={0,3,6},vv[1]={1,4},vv[2]={2,5}。
  2. 一个长度为26的整形数组a,表示当前字母在模板串中出现的位置是该字母对应的数组中的第几个数。这个数组的作用在下面的算法分析中解释。
  3. 一个变量ss,表示遍历到的当前字母的前一个字母在模板串中的位置。这个变量的作用是,规定了在模板串中找子查询串中的字母时,查到的位置必须要大于ss。

算法设计:

  1. 设模板串的长度为N。将模板串中同一个字母所出现的所有位置保存到同一行中,再在每一行的末尾添加一个N。这样就构成了二维数组vv。不难发现,这个二维数组的每一行都是有序的。
  2. 到vv中找出子查询串的第一个字母在模板串中第一次出现的位置,即vv[s1[0]-'a'],设为ss。若没有出现过,即查到的数是N,则输出NO。因为ss是数组中的第一个数,所以将a[s1[0]-'a']设为1;这就表示,如果以后再遇到s1[0]这个字母,我们就要从vv[s1[0]-'a']这个数组的第二个数开始查询了,而不能从第一个数开始查询。因为ss这个位置已经被前面的字母所占了。
  3. 接着看第二个字母,在vv[s1[1]-'a']数组中,从第a[s1[1]-'a']项开始查询第一个大于ss的数。若查到的数是N,则说明子查询串中第二个字母在模板串中出现的位置都比第一个字母在模板串中第一次出现的位置小,返回NO;若找到不是N,将ss更新为找到的这个数。并更新a数组。
  4. 重复2、3,直到遍历完子查询串中的所有字母。这里有个关键的函数,lower_bound函数,用法是在一个有序的数组内,查找一个位置,使插入一个数后不破坏这个数组的有序性。因为vv数组是有序的,所以我们可以用这个函数快速的找到数组中第一个大于ss的数。
#include<bits/stdc++.h>
using namespace std;
char s[100005],s1[100];
vector<int> vv[27];
int a[27];
int main()
{
    scanf("%s",s);
    int len=strlen(s);
    for(int i=0;i<len;i++)
        vv[s[i]-'a'].push_back(i);
    for(int i=0;i<27;i++)
        vv[i].push_back(len);
    
    int n,ans=0;
    scanf("%d",&n);
    while(n--)
	{
        memset(a,0,sizeof(a));
        scanf("%s",s1);
        int leap=0;
        int len1=strlen(s1);
        int ss=vv[s1[0]-'a'][0];
        if(ss==len)
        {
        	printf("NO\n");
            continue;
        }
        a[s1[0]-'a']=1;
        for(int i=1;i<len1;i++)
		{
            int h=lower_bound(vv[s1[i]-'a'].begin()+a[s1[i]-'a'],vv[s1[i]-'a'].end(),ss)-vv[s1[i]-'a'].begin();
            a[s1[i]-'a']=h+1;
            ss=vv[s1[i]-'a'][h];
            if(ss==len)
			{
                leap=1;
                break;
            }
        }
        if(!leap)
        	printf("YES\n");
		else
			printf("NO\n");
    }
    return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值