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; //用当前字符更新当前字符在原串中从后向前最晚出现的位置 } }
题意就是判断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; }
找出全部\(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)