序列自動機

http://www.javashuo.com/article/p-kxzhauyk-gc.htmlhtml

昨天在牛客碰到了這樣的一道題,判斷一些字符串是否是原串的子序列,,,由於以前作過一些LCS子序列的題,,,就想,這不賊簡單,,用lcs求一下每一個子串和原串,,而後判斷LCS的長度是否是等於要判斷的那個串的長度,,,而後,,T了,,,ios

由於dp求LCS幾個串還好說,,可是當串又多又長時,,,不只會T,,dp數組不弄滾動數組還會MLE,,,c++

以後看了題解了解到這個處理子序列的好東西,序列自動機,,,數組

分析

序列自動機實質仍是用空間換時間,,它有一個數組 \(nxt[i][j](nxt[maxn][26]\),,表示原串s的第i位後面那26個字符j出現的最先的 位置,,spa

至關於建了一棵樹,,根節點是一個空節點,,,它有26個孩子,,表示每個字母最先出現的位置,,,那麼原串的第一個字符 \(s[0]\) 就使得 \(nxt[0][s[0] - 'a'] = 1\),,第二個字符就是 \(nxt[0][s[1]-'a']=2\),,,等等等等,,,一樣第一個字符也有這樣的26個孩子,,,這樣從根節點到任意一個葉子節點都是原串的一個子序列,,.net

這樣判斷一個字符串t是否是原串的子序列只要將t中的每個字符在那棵樹裏跑一下,,,若是存在這樣的路徑就表示t是s的一個子序列,,,code

那麼怎麼建樹呢,,htm

若是正着建樹的話每次都要找到後面最先出現的字符的位置,,,不太好弄,,因此咱們倒着建樹,,用一個 \(now[26]\) 數組表示遍歷到第i個字符時後面這26個字符從後往前看最晚出現的位置,,也就是第i個字符後面的26個字符最在出現的位置,,,用它來更新 \(nxt[i][1 \to 26]\),,而後再將這個字符在 \(now\) 數組中的位置更新爲當前的位置,,\(now[s[i]-'a']=i\),,,blog

最後的實現就是這樣子:ci

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;                //用當前字符更新當前字符在原串中從後向前最晚出現的位置
    }
}

例題

牛客392-j

題意

題意就是判斷n個字符串是否是原串的子序列,,,

代碼

#include <bits/stdc++.h>
#define aaa cout<<233<<endl;
#define endl '\n'
#define pb push_back
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int inf = 0x3f3f3f3f;//1061109567
const ll linf = 0x3f3f3f3f3f3f3f;
const double eps = 1e-6;
const double pi = 3.14159265358979;
const int maxn = 1e6 + 5;
const int maxm = 1e5 + 5;
const int mod = 1e9 + 7;
inline int read() {
    char c = getchar(); int x = 0, f = 1;
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
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;                //用當前字符更新當前字符在原串中從後向前最晚出現的位置
    }
}
char ss[maxn];
int main()
{
//    freopen("233.in" , "r" , stdin);
//    freopen("233.out" , "w" , stdout);
//    ios_base::sync_with_stdio(0);
//    cin.tie(0);cout.tie(0);
    scanf("%s", s);
    int n; scanf("%d", &n);
    init();
    while(n--)
    {
        scanf("%s", ss);
        int loc = now[ss[0] - 'a'];             //沒有以子串第一個字符出現的子序列時
        if(!~loc)printf("No\n");
        else
        {
            bool flag = true;
            int len = strlen(ss);
            for(int i = 1; i < len; ++i)
            {
                loc = nxt[loc][ss[i] - 'a'];    //尋找母串中子串第i個字符下一次出現的位置
                if(!~loc)                       //沒有就退出
                {
                    flag = false;
                    break;
                }
            }
            if(flag)printf("Yes\n");
            else    printf("No\n");
        }
    }
    return 0;
}

牛客156-d

題意

找出全部\(abcdefghi\)的排列是原串的子序列的個數,,,

判斷條件改一下就好了,,

代碼

#include <bits/stdc++.h>
#define aaa cout<<233<<endl;
#define endl '\n'
#define pb push_back
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int inf = 0x3f3f3f3f;//1061109567
const ll linf = 0x3f3f3f3f3f3f3f;
const double eps = 1e-6;
const double pi = 3.14159265358979;
const int maxn = 1e6 + 5;
const int maxm = 1e5 + 5;
const int mod = 1e9 + 7;
inline int read() {
    char c = getchar(); int x = 0, f = 1;
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
int now[10];
int nxt[maxn][10];
char s[maxn];
void init()
{
    memset(now, -1, sizeof now);
    int len = strlen(s);
    --len;
    for(int i = len; ~i; --i)
    {
        for(int j = 0; j < 9; ++j)
            nxt[i][j] = now[j];
        now[s[i] - 'a'] = i;
    }
}
int main()
{
//    freopen("233.in" , "r" , stdin);
//    freopen("233.out" , "w" , stdout);
//    ios_base::sync_with_stdio(0);
//    cin.tie(0);cout.tie(0);
    scanf("%s", s);
    int ans = 0;
    char a[] = "abcdefghi";
    init();
    do
    {
        int loc = now[a[0] - 'a'];
        if(!~loc)continue;
        for(int i = 1; i < 9; ++i)
        {
            loc = nxt[loc][a[i] - 'a'];
            if(!~loc)break;
        }
        if(s[loc] == a[8])++ans;
    }while(next_permutation(a, a + 9));
    printf("%d", ans);
    return 0;
}

還有一些序列自動機加dp什麼的,,,之後再看把,,,

這裏有不少知識點

(end)

相關文章
相關標籤/搜索