序列自动机

166 篇文章 0 订阅

昨天在牛客碰到了这样的一道题,判断一些字符串是不是原串的子序列,,,因为之前做过一些LCS子序列的题,,,就想,这不贼简单,,用lcs求一下每个子串和原串,,然后判断LCS的长度是不是等于要判断的那个串的长度,,,然后,,T了,,,

因为dp求LCS几个串还好说,,但是当串又多又长时,,,不仅会T,,dp数组不弄滚动数组还会MLE,,,

之后看了题解了解到这个处理子序列的好东西,序列自动机,,,

分析

序列自动机实质还是用空间换时间,,它有一个数组 nxt[i][j](nxt[maxn][26]nxt[i][j](nxt[maxn][26],,表示原串s的第i位后面那26个字符j出现的最早的 位置,,

相当于建了一棵树,,根节点是一个空节点,,,它有26个孩子,,表示每一个字母最早出现的位置,,,那么原串的第一个字符 s[0]s[0] 就使得 nxt[0][s[0]−′a′]=1nxt[0][s[0]−′a′]=1,,第二个字符就是 nxt[0][s[1]−′a′]=2nxt[0][s[1]−′a′]=2,,,等等等等,,,同样第一个字符也有这样的26个孩子,,,这样从根节点到任意一个叶子节点都是原串的一个子序列,,

这样判断一个字符串t是不是原串的子序列只要将t中的每一个字符在那棵树里跑一下,,,如果存在这样的路径就表示t是s的一个子序列,,,

那么怎么建树呢,,

如果正着建树的话每次都要找到后面最早出现的字符的位置,,,不太好弄,,所以我们倒着建树,,用一个 now[26]now[26] 数组表示遍历到第i个字符时后面这26个字符从后往前看最晚出现的位置,,也就是第i个字符后面的26个字符最在出现的位置,,,用它来更新 nxt[i][1→26]nxt[i][1→26],,然后再将这个字符在 nownow 数组中的位置更新为当前的位置,,now[s[i]−′a′]=inow[s[i]−′a′]=i,,,

最后的实现就是这样子:

int nxt[maxn][30];
int now[30];
char s[maxn];
void init()
{
    //序列自动机预处理
    memset(now, -1, sizeof now);            //mow_i表示第i个字母在原串中从后向前最晚出现的位置
    int len = strlen(s);
    --len;
    for(int i = len; ~i; --i)               //处理每一个字符
    {
        for(int j = 0; j < 26; ++j)        //找出第i个字符后面的26个字母最早出现的字符的位置
            nxt[i][j] = now[j];
        now[s[i] - 'a'] = i;                //用当前字符更新当前字符在原串中从后向前最晚出现的位置
    }
}

例题:

链接:https://ac.nowcoder.com/acm/contest/392/J
来源:牛客网
 

题目描述

月月和华华一起去吃饭了。期间华华有事出去了一会儿,没有带手机。月月出于人类最单纯的好奇心,打开了华华的手机。哇,她看到了一片的QQ推荐好友,似乎华华还没有浏览过。月月顿时醋意大发,出于对好朋友的关心,为了避免华华浪费太多时间和其他网友聊天,她要删掉一些推荐好友。但是为了不让华华发现,产生猜疑,破坏了他们的友情,月月决定只删华华有可能搭讪的推荐好友。
月月熟知华华搭讪的规则。华华想与某个小姐姐搭讪,当且仅当小姐姐的昵称是他的昵称的子序列。为了方便,华华和小姐姐的昵称只由小写字母构成。为了更加方便,保证小姐姐的昵称长度不会比华华的长。
现在月月要快速的判断出哪些推荐好友要删掉,因为华华快回来了,时间紧迫,月月有点手忙脚乱,所以你赶紧写个程序帮帮她吧!

输入描述:

第一行输入一个字符串A表示华华的昵称。
第二行输入一个正整数N表示华华的推荐好友的个数。
接下来N行,每行输入一个字符串BiBi表示某个推荐好友的昵称。

输出描述:

输出N行,对于第i个推荐好友,如果华华可能向她搭讪,输出Yes,否则输出No。
注意大写,同时也要注意输出效率对算法效率的影响。

代码:

#include<bits/stdc++.h>
using namespace std;
int nxt[1000006][27],now[27],n;
string s,t;
int main(){
    fill(now,now+27,-1);
    cin>>s;
    int len=s.size();
    for(int i=len-1;i>=0;i--){
        for(int j=0;j<26;j++)
            nxt[i][j]=now[j];//表示第i个字符的下一字母出现的位置(在原串中的下标)
        now[s[i]-'a']=i;
    }//序列自动机初始化
    cin>>n;
    while(n--){
        cin>>t;
        bool flag=true;
        len=t.size();
        int lac=now[t[0]-'a'];
        if(lac==-1){printf("No\n");}
        else {
            for(int i=1;i<len;i++){
                lac=nxt[lac][t[i]-'a'];
                if(lac==-1){
                    flag=false;
                    break;
                }
            }
            if(flag)printf("Yes\n");
            else printf("No\n");
        }
    }
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值