序列自动机
https://www.cnblogs.com/31415926535x/p/10504504.html
昨天在牛客碰到了这样的一道题,判断一些字符串是不是原串的子序列,,,因为之前做过一些LCS子序列的题,,,就想,这不贼简单,,用lcs求一下每个子串和原串,,然后判断LCS的长度是不是等于要判断的那个串的长度,,,然后,,T了,,,
因为dp求LCS几个串还好说,,但是当串又多又长时,,,不仅会T,,dp数组不弄滚动数组还会MLE,,,
之后看了题解了解到这个处理子序列的好东西,序列自动机,,,
分析
序列自动机实质还是用空间换时间,,它有一个数组 \(nxt[i][j](nxt[maxn][26]\),,表示原串s的第i位后面那26个字符j出现的最早的 位置,,
相当于建了一棵树,,根节点是一个空节点,,,它有26个孩子,,表示每一个字母最早出现的位置,,,那么原串的第一个字符 \(s[0]\) 就使得 \(nxt[0][s[0] – \’a\’] = 1\),,第二个字符就是 \(nxt[0][s[1]-\’a\’]=2\),,,等等等等,,,同样第一个字符也有这样的26个孩子,,,这样从根节点到任意一个叶子节点都是原串的一个子序列,,
这样判断一个字符串t是不是原串的子序列只要将t中的每一个字符在那棵树里跑一下,,,如果存在这样的路径就表示t是s的一个子序列,,,
那么怎么建树呢,,
如果正着建树的话每次都要找到后面最早出现的字符的位置,,,不太好弄,,所以我们倒着建树,,用一个 \(now[26]\) 数组表示遍历到第i个字符时后面这26个字符从后往前看最晚出现的位置,,也就是第i个字符后面的26个字符最在出现的位置,,,用它来更新 \(nxt[i][1 \to 26]\),,然后再将这个字符在 \(now\) 数组中的位置更新为当前的位置,,\(now[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; //用当前字符更新当前字符在原串中从后向前最晚出现的位置
}
}
例题
牛客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)