今天剛學了序列自動機感覺挺妙的;
這個就是給你一個母串,再給一下子串讓你判斷哪些子串是他的子串
這時候我們可以先對母串進行預處理一下:
用一個二維數來記錄第i個位置後面的每個字母出現的第一個位置,dp[i][j]表示第 i 個位置以後字母 j 第一次出現的位置;當這個預處理結束後我們在查找的時候就可以找到這個字母的位置後再從這個位置查找下個字符這樣一直跳着來查詢就可以很快的查找結束了
預處理
我們可以從後向前慢慢的遍歷這樣一個循環就好了,但是注意存儲的時候需要從第一個數開始,初始化的時候把數組初始化爲 -1 ;比如 第 i+1 個字符是 a 那麼dp[i][a]=i+1;其他的字符都是dp[i][b]=dp[i+1][b];
查找
i=0;
直接從dp[i][x] (x爲需要判斷的子串的第一個字符);然後每次更新 i 的位置,順序的遍歷需要判斷的子串的每個字符就可以了,一旦遇到 -1 就結束說明不可能是;
下面看個例題吧
子串查詢:題目鏈接
時間限制:C/C++ 1秒,其他語言2秒
空間限制:C/C++ 32768K,其他語言65536K
64bit IO Format: %lld
題目描述
給出一個長度爲n的字符串s和q個查詢。對於每一個查詢,會輸入一個字符串t,你需要判斷這個字符串t是不是s的子串。子串的定義就是存在任意下標a<b<c<d<e,那麼”s[a]s[b]s[c]s[d]s[e]”就構成s的一個子串。如”abc”的子串有”a”、”b”、”c”、”ab”、”ac”、”bc”、”abc”。
輸入描述:
第一行兩個數n,q。1<=n,q<=1e5。
第二行一個長度爲n的字符串s,所有字符都爲小寫拉丁字符。
接下來q行每行一個字符串t。1<=|t|<=50。
輸出描述:
對於每個查詢,如果t是s的字串,輸出”YES”,否則輸出”NO”。每個答案佔一行。
輸入
8 4
ababcbaa
abac
accb
aaaa
abcba
輸出
YES
NO
YES
YES
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
char a[maxn],b[maxn];
int n,m;
int dp[maxn][30];
//構建動態的數組
void build()
{
memset(dp,-1,sizeof dp);//初始化的值只要不可能出現就行
//記錄這個位置以後最先出現的字符
for(int j=n-1;j>=0;j--)//從倒數二個位置開始遍歷
{
for(int i=0;i<26;i++)// 判斷這個位置的下個字符 i 的位置
{
if(a[j+1]-'a'==i)// 如果下個字符是 i 就更新位置
dp[j][i]=j+1;
else dp[j][i]=dp[j+1][i];// 如果不等就 和上個位置一樣
}
}
}
int main()
{
//因爲進行預處理的時候是這個位置以後最先出現的字符
//所以不能從0開始存字符 ,要從1開始
scanf("%d%d%s",&n,&m,a+1);
build();
while(m--)
{
scanf("%s",b);
int le=strlen(b);
bool flag=0;
int j=0;//初始化 j=0;
for(int i=0;i<le;i++)//順序遍歷子串的每個字符
{
// 如果等於-1就結束
if(dp[j][b[i]-'a']==-1){
flag=1;
break;
}
j=dp[j][b[i]-'a'];//更新j的位置
}
puts(flag?"NO":"YES");
}
return 0;
}