利用【序列自动机】在字符串中遍历寻找子串

题目描述

        对于任意给定的字符串s,已知t为s的子串,寻找到t中每一位字母依照原来顺序排列时,在母串s中的坐标集(但不必连续)。期望得到所有可能的坐标集中字典序最小的一则。

输入

        第一行输入字符串s,长度小于200,000。

        第二行输入字符串t,长度小于10,000。

输出

        字典序最小的一行整数,代表t中每一个字符在s中的位置。

示例

测试输入

iidsfnxksksndaafdsignksdmweosaf
sign

期望输出

1 20 21 23

        注意这里按照sign排序则得到4 19 20 21,但由于按照igns排序得到1 20 21 23,按照字典序最小的要求,显然由于1 < 4 故而最终答案是1 20 21 23。

解题

        显然对于二十万和一万的字符串长度,通过序列自动机可以更方便(节省时间)得到结果。

        先构造序列自动机的函数:

int now[30]; //记录字母最早出现的位置  
int nex[MAXN][30]; //nex[i][j]记录在主串第i个字符之后('a'+j)第一次出现的位置 

void init(){  
    memset(now,-1,sizeof(now));  
    int lens=strlen(s);   
      
    for(int i = lens - 1;i >= 0;i--){  
        for(int j = 0;j < 26;j++)  
            nex[i][j] = now[j];
        now[s[i] - 'a'] = i;
    }  
}

        注意需要建立外部的大循环来遍历每一次t。

        可以如下在第一次进行读取时就判断字典序,这样可以在前面不满足条件的时候直接continue下一轮,避免在无意义段落上浪费时间。

#include <iostream>  
#include <algorithm>  
#include <cstring>   
#define MAXN 200000  
#define LONG 10000  
using namespace std;  
  
char s[MAXN], t[LONG], target[LONG];  
int now[30];//记录字母最早出现的位置  
int nex[MAXN][30];//nex[i][j]记录在主串第i个字符之后('a'+j)第一次出现的位置  
int ans[MAXN], cmpr[MAXN];  
int temp = LONG;  
int flag_dblout = 0, flag_cmpr = 0;  

void init(){  
    memset(now,-1,sizeof(now));  
    int lens=strlen(s);   
      
    for(int i=lens-1;i>=0;i--){  
        for(int j=0;j<26;j++)  
            nex[i][j]=now[j];
        now[s[i]-'a']=i;  
    }  
}  

int main(){  
    memset(cmpr, 0, sizeof(cmpr));  
    memset(ans, MAXN, sizeof(ans));  
    int loc,len;  
    scanf("%s",s);  
    getchar();  
    scanf("%s",target);  
    len = strlen(target);  
    init();  
    int n = 0;  
    while(n < len){ //主轮回   
        flag_dblout = 0;  
        flag_cmpr = 0;  
        n++;  
        for(int g=0;g<len-n;g++){ //换序部分  
            t[g]=target[g+n];   
        } for(int g=len-n;g<len;g++){  
            t[g]=target[g+n-len];   
        }  
        //cout << "###########下面开始第"<< n <<"次轮回###########" << endl << "轮回项目是:"<< t << endl; //【检验代码】  
          
        loc = now[t[0]-'a'];  //特判   
        if (loc <= ans[0]) {  
            cmpr[0] = loc;  
            if(loc == ans[0])flag_cmpr = 1; //首位如果相等就做记号留到后面再比较   
        } else continue; //首位并没有比先前成功案例小或一样就直接走下一轮   
        //cout << "************" << ans[0] +1 << endl; //【检验代码】打印第一个字符首次出现位置   
        //cout << cmpr[0] +1 << endl; //【检验代码】打印第一个字符首次出现位置   
        //具体实现寻找后续字母位置   
        for(int i=1;i<len;i++){  
            loc=nex[loc][t[i]-'a'];  
            if (flag_cmpr == 0) cmpr[i] = loc; //前一位如果已经小了就直接替换   
            else {  
                if(loc <= ans[i]) { //这一位如果依然相等就做记号留到后面再比较  
                    cmpr[i] = loc;  
                    if(loc == ans[i])flag_cmpr = 1;   
                }  
                else {//首位并没有比先前成功案例小就做记号来直接走下一轮  
                    flag_dblout = -1;  
                    break;   
                }   
            }  
            //cout << "**********" << ans[i]+1 << endl; //【检验代码】打印下一个字符在前一个字符出现后首次出现位置   
            //cout << cmpr[i]+1 << endl; //【检验代码】打印下一个字符在前一个字符出现后首次出现位置                   
            if(loc==-1){ //找不到就直接跳出循环   
                //cout << "!!!!!!!!!!!!!!!!No!!!!!!!!!!!!!!!!" << endl; //【检验代码】  
                break;  
            }  
        } if(loc==-1||flag_dblout==-1)continue; //找不到跳出循环后直接走下一轮
          
        for(int i=0;i<len;i++){  
            ans[i] = cmpr[i];
        }   
    }  
      
    printf("%d",ans[0]+1);  
    for(int i = 1;i<len;i++){  
        printf(" %d",ans[i]+1);  
    } printf("\n");  
    return 0;  
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值